Commit 9f415e84 by Ryan McKinley Committed by GitHub

Refactor: move end-to-end test infrastructure to @grafana/toolkit (#18012)

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