Переглянути джерело

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

Ryan McKinley 6 роки тому
батько
коміт
9f415e84b4

+ 2 - 2
jest.config.e2e.js

@@ -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'],
+};

+ 8 - 0
packages/grafana-toolkit/package.json

@@ -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"
   }
 }

+ 0 - 0
public/e2e-test/core/constants.ts → packages/grafana-toolkit/src/e2e/constants.ts


+ 12 - 12
public/e2e-test/core/images.ts → packages/grafana-toolkit/src/e2e/images.ts

@@ -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 - 0
packages/grafana-toolkit/src/e2e/index.ts

@@ -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 - 1
public/e2e-test/install/install.ts → packages/grafana-toolkit/src/e2e/install.ts

@@ -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();

+ 0 - 0
public/e2e-test/core/launcher.ts → packages/grafana-toolkit/src/e2e/launcher.ts


+ 4 - 4
public/e2e-test/core/login.ts → packages/grafana-toolkit/src/e2e/login.ts

@@ -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();
 };
 

+ 2 - 2
public/e2e-test/core/pageObjects.ts → packages/grafana-toolkit/src/e2e/pageObjects.ts

@@ -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);
   };
 }

+ 11 - 12
public/e2e-test/core/pages.ts → packages/grafana-toolkit/src/e2e/pages.ts

@@ -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 = () => {

+ 2 - 2
public/e2e-test/core/scenario.ts → packages/grafana-toolkit/src/e2e/scenario.ts

@@ -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();

+ 4 - 4
public/e2e-test/pages/start/loginPage.ts → packages/grafana-toolkit/src/e2e/start/loginPage.ts

@@ -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 - 0
packages/grafana-toolkit/src/index.ts

@@ -0,0 +1 @@
+export * from './e2e';

+ 1 - 2
public/e2e-test/pages/dashboards/createDashboardPage.ts

@@ -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 - 2
public/e2e-test/pages/dashboards/dashboardsPage.ts

@@ -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;

+ 2 - 2
public/e2e-test/pages/dashboards/saveDashboardModal.ts

@@ -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 - 2
public/e2e-test/pages/datasources/addDataSourcePage.ts

@@ -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 - 1
public/e2e-test/pages/datasources/dataSources.ts

@@ -1,4 +1,4 @@
-import { TestPage } from 'e2e-test/core/pages';
+import { TestPage } from '@grafana/toolkit';
 
 export interface DataSourcesPage {}
 

+ 2 - 2
public/e2e-test/pages/datasources/editDataSourcePage.ts

@@ -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;

+ 5 - 3
public/e2e-test/pages/panels/editPanel.ts

@@ -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 - 2
public/e2e-test/pages/panels/panel.ts

@@ -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 - 2
public/e2e-test/pages/panels/sharePanelModal.ts

@@ -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 - 3
public/e2e-test/scenarios/smoke.test.ts

@@ -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',