mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Refactor: move end-to-end test infrastructure to @grafana/toolkit (#18012)
This commit is contained in:
parent
ccf11fb7a2
commit
9f415e84b4
@ -11,5 +11,5 @@ module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
||||
setupFiles: [],
|
||||
globals: { 'ts-jest': { isolatedModules: true } },
|
||||
setupFilesAfterEnv: ['expect-puppeteer', '<rootDir>/public/e2e-test/install/install.ts'],
|
||||
};
|
||||
setupFilesAfterEnv: ['expect-puppeteer', '<rootDir>/packages/grafana-toolkit/src/e2e/install.ts'],
|
||||
};
|
@ -19,14 +19,17 @@
|
||||
},
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.4.5",
|
||||
"@babel/preset-env": "7.4.5",
|
||||
"@types/execa": "^0.9.0",
|
||||
"@types/expect-puppeteer": "3.3.1",
|
||||
"@types/inquirer": "^6.0.3",
|
||||
"@types/jest": "24.0.13",
|
||||
"@types/jest-cli": "^23.6.0",
|
||||
"@types/node": "^12.0.4",
|
||||
"@types/puppeteer-core": "1.9.0",
|
||||
"@types/react-dev-utils": "^9.0.1",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/tmp": "^0.1.0",
|
||||
@ -40,6 +43,7 @@
|
||||
"copy-webpack-plugin": "5.0.3",
|
||||
"css-loader": "^3.0.0",
|
||||
"execa": "^1.0.0",
|
||||
"expect-puppeteer": "4.1.1",
|
||||
"file-loader": "^4.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"html-loader": "0.5.5",
|
||||
@ -57,6 +61,7 @@
|
||||
"postcss-loader": "3.0.0",
|
||||
"postcss-preset-env": "6.6.0",
|
||||
"prettier": "^1.18.2",
|
||||
"puppeteer-core": "1.18.1",
|
||||
"react-dev-utils": "^9.0.1",
|
||||
"replace-in-file": "^4.1.0",
|
||||
"replace-in-file-webpack-plugin": "^1.0.6",
|
||||
@ -78,5 +83,8 @@
|
||||
"devDependencies": {
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/prettier": "^1.16.4"
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"puppeteer": "node_modules/puppeteer-core"
|
||||
}
|
||||
}
|
||||
|
@ -25,17 +25,17 @@ export const compareScreenShots = async (fileName: string) =>
|
||||
|
||||
if (screenShotFromTest.width !== screenShotFromTruth.width) {
|
||||
throw new Error(
|
||||
`The screenshot:[${fileName}] taken during the test has a width:[${
|
||||
screenShotFromTest.width
|
||||
}] that differs from the expected: [${screenShotFromTruth.width}].`
|
||||
`The screenshot:[${fileName}] taken during the test has a ` +
|
||||
`width:[${screenShotFromTest.width}] that differs from the ` +
|
||||
`expected: [${screenShotFromTruth.width}].`
|
||||
);
|
||||
}
|
||||
|
||||
if (screenShotFromTest.height !== screenShotFromTruth.height) {
|
||||
throw new Error(
|
||||
`The screenshot:[${fileName}] taken during the test has a width:[${
|
||||
screenShotFromTest.height
|
||||
}] that differs from the expected: [${screenShotFromTruth.height}].`
|
||||
`The screenshot:[${fileName}] taken during the test has a ` +
|
||||
`height:[${screenShotFromTest.height}] that differs from the ` +
|
||||
`expected: [${screenShotFromTruth.height}].`
|
||||
);
|
||||
}
|
||||
|
||||
@ -50,14 +50,14 @@ export const compareScreenShots = async (fileName: string) =>
|
||||
);
|
||||
|
||||
if (numDiffPixels !== 0) {
|
||||
const localMessage = `\nCompare the output from expected:[${constants.screenShotsTruthDir}] with outcome:[${
|
||||
constants.screenShotsOutputDir
|
||||
}]`;
|
||||
const localMessage =
|
||||
`\nCompare the output from expected:[${constants.screenShotsTruthDir}] ` +
|
||||
`with outcome:[${constants.screenShotsOutputDir}]`;
|
||||
const circleCIMessage = '\nCheck the Artifacts tab in the CircleCi build output for the actual screenshots.';
|
||||
const checkMessage = process.env.CIRCLE_SHA1 ? circleCIMessage : localMessage;
|
||||
let msg = `\nThe screenshot:[${
|
||||
constants.screenShotsOutputDir
|
||||
}/${fileName}.png] taken during the test differs by:[${numDiffPixels}] pixels from the expected.`;
|
||||
let msg =
|
||||
`\nThe screenshot:[${constants.screenShotsOutputDir}/${fileName}.png] ` +
|
||||
`taken during the test differs by:[${numDiffPixels}] pixels from the expected.`;
|
||||
msg += '\n';
|
||||
msg += checkMessage;
|
||||
msg += '\n';
|
8
packages/grafana-toolkit/src/e2e/index.ts
Normal file
8
packages/grafana-toolkit/src/e2e/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './constants';
|
||||
export * from './images';
|
||||
export * from './install';
|
||||
export * from './launcher';
|
||||
export * from './login';
|
||||
export * from './pageObjects';
|
||||
export * from './pages';
|
||||
export * from './scenario';
|
@ -1,5 +1,5 @@
|
||||
import puppeteer from 'puppeteer-core';
|
||||
import { constants } from 'e2e-test/core/constants';
|
||||
import { constants } from './constants';
|
||||
|
||||
export const downloadBrowserIfNeeded = async (): Promise<void> => {
|
||||
const browserFetcher = puppeteer.createBrowserFetcher();
|
@ -1,15 +1,15 @@
|
||||
import { Page } from 'puppeteer-core';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { loginPage } from 'e2e-test/pages/start/loginPage';
|
||||
import { loginPage } from './start/loginPage';
|
||||
|
||||
export const login = async (page: Page) => {
|
||||
await loginPage.init(page);
|
||||
await loginPage.navigateTo();
|
||||
|
||||
await loginPage.pageObjects.username.enter('admin');
|
||||
await loginPage.pageObjects.password.enter('admin');
|
||||
await loginPage.pageObjects.submit.click();
|
||||
await loginPage.pageObjects!.username.enter('admin');
|
||||
await loginPage.pageObjects!.password.enter('admin');
|
||||
await loginPage.pageObjects!.submit.click();
|
||||
await loginPage.waitForResponse();
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ export interface SelectPageObjectType extends PageObjectType {
|
||||
}
|
||||
|
||||
export class PageObject implements PageObjectType {
|
||||
protected page: Page = null;
|
||||
protected page?: Page;
|
||||
|
||||
constructor(protected selector: string) {}
|
||||
|
||||
@ -82,6 +82,6 @@ export class SelectPageObject extends PageObject implements SelectPageObjectType
|
||||
select = async (text: string): Promise<void> => {
|
||||
console.log(`Trying to select text:${text} in dropdown:`, this.selector);
|
||||
await expect(this.page).not.toBeNull();
|
||||
await this.page.select(this.selector, text);
|
||||
await this.page!.select(this.selector, text);
|
||||
};
|
||||
}
|
@ -17,7 +17,8 @@ export interface TestPageType<T> {
|
||||
waitForResponse: () => Promise<void>;
|
||||
waitForNavigation: () => Promise<void>;
|
||||
waitFor: (milliseconds: number) => Promise<void>;
|
||||
pageObjects: PageObjects<T>;
|
||||
|
||||
pageObjects?: PageObjects<T>;
|
||||
}
|
||||
|
||||
type PageObjects<T> = { [P in keyof T]: T[P] };
|
||||
@ -28,17 +29,15 @@ export interface TestPageConfig<T> {
|
||||
}
|
||||
|
||||
export class TestPage<T> implements TestPageType<T> {
|
||||
pageObjects: PageObjects<T> = null;
|
||||
private page: Page = null;
|
||||
private pageUrl: string = null;
|
||||
pageObjects?: PageObjects<T>;
|
||||
private page?: Page;
|
||||
private pageUrl?: string;
|
||||
|
||||
constructor(config: TestPageConfig<T>) {
|
||||
if (config.url) {
|
||||
this.pageUrl = `${constants.baseUrl}${config.url}`;
|
||||
}
|
||||
if (config.pageObjects) {
|
||||
this.pageObjects = config.pageObjects;
|
||||
}
|
||||
this.pageObjects = config.pageObjects;
|
||||
}
|
||||
|
||||
init = async (page: Page): Promise<void> => {
|
||||
@ -59,7 +58,7 @@ export class TestPage<T> implements TestPageType<T> {
|
||||
this.throwIfNotInitialized();
|
||||
|
||||
console.log('Trying to navigate to:', this.pageUrl);
|
||||
await this.page.goto(this.pageUrl);
|
||||
await this.page!.goto(this.pageUrl!);
|
||||
};
|
||||
|
||||
expectSelector = async (config: ExpectSelectorConfig): Promise<void> => {
|
||||
@ -75,19 +74,19 @@ export class TestPage<T> implements TestPageType<T> {
|
||||
waitForResponse = async (): Promise<void> => {
|
||||
this.throwIfNotInitialized();
|
||||
|
||||
await this.page.waitForResponse(response => response.url() === this.pageUrl && response.status() === 200);
|
||||
await this.page!.waitForResponse(response => response.url() === this.pageUrl && response.status() === 200);
|
||||
};
|
||||
|
||||
waitForNavigation = async (): Promise<void> => {
|
||||
this.throwIfNotInitialized();
|
||||
|
||||
await this.page.waitForNavigation();
|
||||
await this.page!.waitForNavigation();
|
||||
};
|
||||
|
||||
getUrl = async (): Promise<string> => {
|
||||
this.throwIfNotInitialized();
|
||||
|
||||
return await this.page.url();
|
||||
return await this.page!.url();
|
||||
};
|
||||
|
||||
getUrlWithoutBaseUrl = async (): Promise<string> => {
|
||||
@ -101,7 +100,7 @@ export class TestPage<T> implements TestPageType<T> {
|
||||
waitFor = async (milliseconds: number) => {
|
||||
this.throwIfNotInitialized();
|
||||
|
||||
await this.page.waitFor(milliseconds);
|
||||
await this.page!.waitFor(milliseconds);
|
||||
};
|
||||
|
||||
private throwIfNotInitialized = () => {
|
@ -8,8 +8,8 @@ export const e2eScenario = (
|
||||
callback: (browser: Browser, page: Page) => void
|
||||
) => {
|
||||
describe(title, () => {
|
||||
let browser: Browser = null;
|
||||
let page: Page = null;
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
|
||||
beforeAll(async () => {
|
||||
browser = await launchBrowser();
|
@ -1,11 +1,11 @@
|
||||
import { TestPage } from '../pages';
|
||||
import {
|
||||
InputPageObject,
|
||||
ClickablePageObject,
|
||||
Selector,
|
||||
InputPageObject,
|
||||
InputPageObjectType,
|
||||
ClickablePageObjectType,
|
||||
} from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
ClickablePageObject,
|
||||
} from '../pageObjects';
|
||||
|
||||
export interface LoginPage {
|
||||
username: InputPageObjectType;
|
1
packages/grafana-toolkit/src/index.ts
Normal file
1
packages/grafana-toolkit/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './e2e';
|
@ -1,5 +1,4 @@
|
||||
import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
import { TestPage, ClickablePageObjectType, ClickablePageObject, Selector } from '@grafana/toolkit';
|
||||
|
||||
export interface CreateDashboardPage {
|
||||
addQuery: ClickablePageObjectType;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
import { TestPage, ClickablePageObjectType, ClickablePageObject, Selector } from '@grafana/toolkit';
|
||||
|
||||
export interface DashboardsPage {
|
||||
dashboard: ClickablePageObjectType;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import {
|
||||
TestPage,
|
||||
ClickablePageObjectType,
|
||||
ClickablePageObject,
|
||||
Selector,
|
||||
InputPageObjectType,
|
||||
InputPageObject,
|
||||
PageObject,
|
||||
} from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
} from '@grafana/toolkit';
|
||||
|
||||
export interface SaveDashboardModal {
|
||||
name: InputPageObjectType;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ClickablePageObject, Selector, ClickablePageObjectType } from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
import { TestPage, ClickablePageObject, Selector, ClickablePageObjectType } from '@grafana/toolkit';
|
||||
|
||||
export interface AddDataSourcePage {
|
||||
testDataDB: ClickablePageObjectType;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
import { TestPage } from '@grafana/toolkit';
|
||||
|
||||
export interface DataSourcesPage {}
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {
|
||||
TestPage,
|
||||
ClickablePageObjectType,
|
||||
PageObjectType,
|
||||
ClickablePageObject,
|
||||
PageObject,
|
||||
Selector,
|
||||
} from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
} from '@grafana/toolkit';
|
||||
|
||||
export interface EditDataSourcePage {
|
||||
saveAndTest: ClickablePageObjectType;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {
|
||||
TestPage,
|
||||
SelectPageObjectType,
|
||||
SelectPageObject,
|
||||
Selector,
|
||||
ClickablePageObjectType,
|
||||
ClickablePageObject,
|
||||
} from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
} from '@grafana/toolkit';
|
||||
|
||||
export interface EditPanelPage {
|
||||
queriesTab: ClickablePageObjectType;
|
||||
@ -20,7 +20,9 @@ export const editPanelPage = new TestPage<EditPanelPage>({
|
||||
queriesTab: new ClickablePageObject(Selector.fromAriaLabel('Queries tab button')),
|
||||
saveDashboard: new ClickablePageObject(Selector.fromAriaLabel('Save dashboard navbar button')),
|
||||
scenarioSelect: new SelectPageObject(Selector.fromAriaLabel('Scenario Select')),
|
||||
showXAxis: new ClickablePageObject(Selector.fromSelector('[aria-label="X-Axis section"] > gf-form-switch')),
|
||||
showXAxis: new ClickablePageObject(
|
||||
Selector.fromSelector('[aria-label="X-Axis section"] [label=Show] .gf-form-switch')
|
||||
),
|
||||
visualizationTab: new ClickablePageObject(Selector.fromAriaLabel('Visualization tab button')),
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
import { TestPage, ClickablePageObjectType, ClickablePageObject, Selector } from '@grafana/toolkit';
|
||||
|
||||
export interface Panel {
|
||||
panelTitle: ClickablePageObjectType;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ClickablePageObjectType, ClickablePageObject, Selector } from 'e2e-test/core/pageObjects';
|
||||
import { TestPage } from 'e2e-test/core/pages';
|
||||
import { TestPage, ClickablePageObjectType, ClickablePageObject, Selector } from '@grafana/toolkit';
|
||||
|
||||
export interface SharePanelModal {
|
||||
directLinkRenderedImage: ClickablePageObjectType;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Browser, Page, Target } from 'puppeteer-core';
|
||||
|
||||
import { e2eScenario } from 'e2e-test/core/scenario';
|
||||
import { e2eScenario, constants, takeScreenShot, compareScreenShots } from '@grafana/toolkit';
|
||||
import { addDataSourcePage } from 'e2e-test/pages/datasources/addDataSourcePage';
|
||||
import { editDataSourcePage } from 'e2e-test/pages/datasources/editDataSourcePage';
|
||||
import { dataSourcesPage } from 'e2e-test/pages/datasources/dataSources';
|
||||
@ -9,9 +9,7 @@ import { saveDashboardModal } from 'e2e-test/pages/dashboards/saveDashboardModal
|
||||
import { dashboardsPageFactory } from 'e2e-test/pages/dashboards/dashboardsPage';
|
||||
import { panel } from 'e2e-test/pages/panels/panel';
|
||||
import { editPanelPage } from 'e2e-test/pages/panels/editPanel';
|
||||
import { constants } from 'e2e-test/core/constants';
|
||||
import { sharePanelModal } from 'e2e-test/pages/panels/sharePanelModal';
|
||||
import { takeScreenShot, compareScreenShots } from 'e2e-test/core/images';
|
||||
|
||||
e2eScenario(
|
||||
'Login scenario, create test data source, dashboard, panel, and export scenario',
|
||||
|
Loading…
Reference in New Issue
Block a user