diff --git a/packages/grafana-toolkit/src/e2e/pageInfo.ts b/packages/grafana-toolkit/src/e2e/pageInfo.ts index f60f8f266c2..5bb253151fb 100644 --- a/packages/grafana-toolkit/src/e2e/pageInfo.ts +++ b/packages/grafana-toolkit/src/e2e/pageInfo.ts @@ -1,6 +1,6 @@ import { Page } from 'puppeteer-core'; import { constants } from './constants'; -import { PageObject } from './pageObjects'; +import { PageObject, Selector } from './pageObjects'; export interface ExpectSelectorConfig { selector: string; @@ -22,22 +22,34 @@ export interface TestPageType { } type PageObjects = { [P in keyof T]: T[P] }; +type SelectorFunc = () => string; export interface TestPageConfig { url?: string; - pageObjects: PageObjects; + pageObjects: { [P in keyof T]: string | SelectorFunc }; } export class TestPage implements TestPageType { pageObjects: PageObjects; private page?: Page; - private pageUrl?: string; + private readonly pageUrl?: string; constructor(config: TestPageConfig) { if (config.url) { this.pageUrl = `${constants.baseUrl}${config.url}`; } - this.pageObjects = config.pageObjects; + + this.pageObjects = {} as PageObjects; + Object.keys(config.pageObjects).map(async key => { + const selector = (config.pageObjects as any)[key]; + if (typeof selector === 'function') { + (this.pageObjects as any)[key] = new PageObject(selector()); + } + + if (typeof selector === 'string') { + (this.pageObjects as any)[key] = new PageObject(Selector.fromAriaLabel(selector)); + } + }); } init = async (page: Page): Promise => { diff --git a/packages/grafana-toolkit/src/e2e/pageObjects.ts b/packages/grafana-toolkit/src/e2e/pageObjects.ts index 4c9c77e0a8c..be99bb7d827 100644 --- a/packages/grafana-toolkit/src/e2e/pageObjects.ts +++ b/packages/grafana-toolkit/src/e2e/pageObjects.ts @@ -5,6 +5,10 @@ export class Selector { return `[aria-label="${selector}"]`; }; + static fromSwitchLabel = (selector: string) => { + return `${Selector.fromAriaLabel(selector)} .gf-form-switch input`; + }; + static fromSelector = (selector: string) => { return selector; }; @@ -14,6 +18,7 @@ export interface PageObjectType { init: (page: Page) => Promise; exists: () => Promise; containsText: (text: string) => Promise; + waitForSelector: (timeoutInMs?: number) => Promise; } export interface ClickablePageObjectType extends PageObjectType { @@ -22,13 +27,36 @@ export interface ClickablePageObjectType extends PageObjectType { export interface InputPageObjectType extends PageObjectType { enter: (text: string) => Promise; + containsPlaceholder: (text: string) => Promise; + blur: () => Promise; } export interface SelectPageObjectType extends PageObjectType { select: (text: string) => Promise; + selectedTextIs: (text: string) => Promise; } -export class PageObject implements PageObjectType { +export interface SwitchPageObjectType extends PageObjectType { + toggle: () => Promise; + isSwitchedOn: () => Promise; + isSwitchedOff: () => Promise; +} + +export interface ArrayPageObjectType { + hasLength: (length: number) => Promise; + clickAtPos: (index: number) => Promise; + containsTextAtPos: (text: string, index: number) => Promise; + waitForSelector: (timeoutInMs?: number) => Promise; +} + +export class PageObject + implements + PageObjectType, + ClickablePageObjectType, + InputPageObjectType, + SelectPageObjectType, + SwitchPageObjectType, + ArrayPageObjectType { protected page?: Page; constructor(protected selector: string) {} @@ -38,50 +66,109 @@ export class PageObject implements PageObjectType { }; exists = async (): Promise => { + console.log('Checking for existence of:', this.selector); const options = { visible: true } as any; await expect(this.page).not.toBeNull(); await expect(this.page).toMatchElement(this.selector, options); }; containsText = async (text: string): Promise => { + console.log(`Checking for existence of '${text}' for:`, this.selector); const options = { visible: true, text } as any; await expect(this.page).not.toBeNull(); await expect(this.page).toMatchElement(this.selector, options); }; -} -export class ClickablePageObject extends PageObject implements ClickablePageObjectType { - constructor(selector: string) { - super(selector); - } + containsPlaceholder = async (expectedPlaceholder: string): Promise => { + console.log(`Checking for placeholder '${expectedPlaceholder}' in:`, this.selector); + await expect(this.page).not.toBeNull(); + const placeholder = await this.page!.$eval(this.selector, (input: any) => input.placeholder); + await expect(placeholder).toEqual(expectedPlaceholder); + }; + + hasLength = async (length: number): Promise => { + console.log('Checking for length of', this.selector); + const result = await this.page!.$$eval(this.selector, elements => elements.length); + await expect(result).toEqual(length); + }; + + containsTextAtPos = async (text: string, index: number): Promise => { + console.log(`Checking for text ${text} at position ${index} of`, this.selector); + await expect(this.page).not.toBeNull(); + const result = await this.page!.$$eval(this.selector, elements => elements.map((el: any) => el.innerText)); + await expect(result[index]!.trim()).toEqual(text); + }; click = async (): Promise => { console.log('Trying to click on:', this.selector); await expect(this.page).not.toBeNull(); await expect(this.page).toClick(this.selector); }; -} -export class InputPageObject extends PageObject implements InputPageObjectType { - constructor(selector: string) { - super(selector); - } + clickAtPos = async (index: number): Promise => { + console.log(`Trying to clicking at position:${index} on:`, this.selector); + await expect(this.page).not.toBeNull(); + const elements = await this.page!.$$(this.selector); + const element = await elements[index]; + await element.click(); + }; + + toggle = async (): Promise => { + const switchSelector = this.selector.replace(' .gf-form-switch input', ''); + console.log('Trying to toggle:', switchSelector); + await expect(this.page).not.toBeNull(); + await expect(this.page).toClick(switchSelector); + }; enter = async (text: string): Promise => { console.log(`Trying to enter text:${text} into:`, this.selector); await expect(this.page).not.toBeNull(); await expect(this.page).toFill(this.selector, text); }; -} - -export class SelectPageObject extends PageObject implements SelectPageObjectType { - constructor(selector: string) { - super(selector); - } select = async (text: string): Promise => { console.log(`Trying to select text:${text} in dropdown:`, this.selector); await expect(this.page).not.toBeNull(); await this.page!.select(this.selector, text); }; + + selectedTextIs = async (text: string): Promise => { + console.log(`Trying to get selected text from dropdown:`, this.selector); + await expect(this.page).not.toBeNull(); + const selectedText = await this.page!.$eval(this.selector, (select: any) => { + if (select.selectedIndex === -1) { + return ''; + } + return select.options[select.selectedIndex].innerText; + }); + await expect(selectedText).toEqual(text); + }; + + waitForSelector = async (timeoutInMs?: number): Promise => { + console.log('Waiting for', this.selector); + await expect(this.page).not.toBeNull(); + await this.page!.waitForSelector(this.selector, { timeout: timeoutInMs || 1000 }); + }; + + isSwitchedOn = async (): Promise => { + const checked = await this.getChecked(); + await expect(checked).toBe(true); + }; + + isSwitchedOff = async (): Promise => { + const checked = await this.getChecked(); + await expect(checked).toBe(false); + }; + + blur = async (): Promise => { + console.log('Trying to blur:', this.selector); + await expect(this.page).not.toBeNull(); + await this.page!.$eval(this.selector, (input: any) => input.blur()); + }; + + private getChecked = async (): Promise => { + console.log('Trying get switch status for:', this.selector); + await expect(this.page).not.toBeNull(); + return await this.page!.$eval(this.selector, (input: any) => input.checked); + }; } diff --git a/packages/grafana-toolkit/src/e2e/pages/dashboards/createDashboardPage.ts b/packages/grafana-toolkit/src/e2e/pages/dashboards/createDashboardPage.ts new file mode 100644 index 00000000000..6be5713a618 --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/dashboards/createDashboardPage.ts @@ -0,0 +1,55 @@ +import { Page } from 'puppeteer-core'; + +import { ClickablePageObjectType } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; +import { dashboardPage } from './dashboardPage'; +import { dashboardSettingsPage } from './dashboardSettingsPage'; +import { saveDashboardModal } from './saveDashboardModal'; +import { dashboardsPageFactory } from './dashboardsPage'; +import { confirmModal } from '../modals/confirmModal'; + +export interface CreateDashboardPage { + addQuery: ClickablePageObjectType; + saveDashboard: ClickablePageObjectType; +} + +export const createDashboardPage = new TestPage({ + url: '/dashboard/new', + pageObjects: { + addQuery: 'Add Query CTA button', + saveDashboard: 'Save dashboard navbar button', + }, +}); + +export const createEmptyDashboardPage = async (page: Page, dashboardTitle: string) => { + await createDashboardPage.init(page); + await createDashboardPage.navigateTo(); + await createDashboardPage.pageObjects.saveDashboard.click(); + + await saveDashboardModal.init(page); + await saveDashboardModal.expectSelector({ selector: 'save-dashboard-as-modal' }); + await saveDashboardModal.pageObjects.name.enter(dashboardTitle); + await saveDashboardModal.pageObjects.save.click(); + await saveDashboardModal.pageObjects.success.exists(); + + await dashboardPage.init(page); + return dashboardPage; +}; + +export const cleanDashboard = async (page: Page, dashboardTitle: string) => { + const dashboardsPage = dashboardsPageFactory(dashboardTitle); + await dashboardsPage.init(page); + await dashboardsPage.navigateTo(); + await dashboardsPage.pageObjects.dashboard.exists(); + await dashboardsPage.pageObjects.dashboard.click(); + + await dashboardPage.init(page); + await dashboardPage.pageObjects.settings.click(); + + await dashboardSettingsPage.init(page); + await dashboardSettingsPage.pageObjects.deleteDashBoard.click(); + + await confirmModal.init(page); + await confirmModal.pageObjects.delete.click(); + await confirmModal.pageObjects.success.exists(); +}; diff --git a/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardPage.ts b/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardPage.ts new file mode 100644 index 00000000000..3d12cf45c55 --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardPage.ts @@ -0,0 +1,49 @@ +import { ArrayPageObjectType, ClickablePageObjectType, PageObjectType } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; + +export interface DashboardPage { + settings: ClickablePageObjectType; + submenuItemLabel: ArrayPageObjectType; + submenuItemValueDropDownValueLink: ArrayPageObjectType; + submenuItemValueDropDownDropDown: PageObjectType; + submenuItemValueDropDownSelectedLink: PageObjectType; + submenuItemValueDropDownOptionText: ArrayPageObjectType; +} + +export const dashboardPage = new TestPage({ + pageObjects: { + settings: 'Dashboard settings navbar button', + submenuItemLabel: 'Dashboard template variables submenu LabelName label', + submenuItemValueDropDownValueLink: 'Dashboard template variables Variable Value DropDown value link', + submenuItemValueDropDownDropDown: 'Dashboard template variables Variable Value DropDown DropDown', + submenuItemValueDropDownSelectedLink: 'Dashboard template variables Variable Value DropDown Selected link', + submenuItemValueDropDownOptionText: 'Dashboard template variables Variable Value DropDown option text', + }, +}); + +export interface AssertVariableLabelsAndComponentsArguments { + label: string; + options: string[]; +} + +export const assertVariableLabelsAndComponents = async ( + page: TestPage, + args: AssertVariableLabelsAndComponentsArguments[] +) => { + console.log('Asserting variable components and labels'); + await page.pageObjects.submenuItemLabel.waitForSelector(); + await page.pageObjects.submenuItemLabel.hasLength(args.length); + await page.pageObjects.submenuItemValueDropDownValueLink.hasLength(args.length); + + for (let index = 0; index < args.length; index++) { + const { label, options } = args[index]; + await page.pageObjects.submenuItemLabel.containsTextAtPos(label, index); + await page.pageObjects.submenuItemValueDropDownValueLink.containsTextAtPos(options[1], index); + await page.pageObjects.submenuItemValueDropDownValueLink.clickAtPos(index); + await page.pageObjects.submenuItemValueDropDownOptionText.hasLength(options.length); + for (let optionIndex = 0; optionIndex < options.length; optionIndex++) { + await page.pageObjects.submenuItemValueDropDownOptionText.containsTextAtPos(options[optionIndex], optionIndex); + } + } + console.log('Asserting variable components and labels, Ok'); +}; diff --git a/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardSettingsPage.ts b/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardSettingsPage.ts new file mode 100644 index 00000000000..c74955f3cf2 --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardSettingsPage.ts @@ -0,0 +1,16 @@ +import { ClickablePageObjectType } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; + +export interface DashboardSettingsPage { + deleteDashBoard: ClickablePageObjectType; + variablesSection: ClickablePageObjectType; + saveDashBoard: ClickablePageObjectType; +} + +export const dashboardSettingsPage = new TestPage({ + pageObjects: { + deleteDashBoard: 'Dashboard settings page delete dashboard button', + variablesSection: 'Dashboard settings section Variables', + saveDashBoard: 'Dashboard settings aside actions Save button', + }, +}); diff --git a/public/e2e-test/pages/dashboards/dashboardsPage.ts b/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardsPage.ts similarity index 54% rename from public/e2e-test/pages/dashboards/dashboardsPage.ts rename to packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardsPage.ts index 8c2cb6d7887..4f20ac9b42b 100644 --- a/public/e2e-test/pages/dashboards/dashboardsPage.ts +++ b/packages/grafana-toolkit/src/e2e/pages/dashboards/dashboardsPage.ts @@ -1,4 +1,5 @@ -import { TestPage, ClickablePageObjectType, ClickablePageObject, Selector } from '@grafana/toolkit/src/e2e'; +import { ClickablePageObjectType } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; export interface DashboardsPage { dashboard: ClickablePageObjectType; @@ -8,6 +9,6 @@ export const dashboardsPageFactory = (dashboardTitle: string) => new TestPage({ url: '/dashboards', pageObjects: { - dashboard: new ClickablePageObject(Selector.fromAriaLabel(dashboardTitle)), + dashboard: dashboardTitle, }, }); diff --git a/packages/grafana-toolkit/src/e2e/pages/dashboards/saveChangesDashboardModal.ts b/packages/grafana-toolkit/src/e2e/pages/dashboards/saveChangesDashboardModal.ts new file mode 100644 index 00000000000..1130c192575 --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/dashboards/saveChangesDashboardModal.ts @@ -0,0 +1,14 @@ +import { ClickablePageObjectType, PageObject, Selector } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; + +export interface SaveChangesDashboardModal { + save: ClickablePageObjectType; + success: PageObject; +} + +export const saveChangesDashboardModal = new TestPage({ + pageObjects: { + save: 'Dashboard settings Save Dashboard Modal Save button', + success: () => Selector.fromSelector('.alert-success'), + }, +}); diff --git a/packages/grafana-toolkit/src/e2e/pages/dashboards/saveDashboardModal.ts b/packages/grafana-toolkit/src/e2e/pages/dashboards/saveDashboardModal.ts new file mode 100644 index 00000000000..4e7a7d172ac --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/dashboards/saveDashboardModal.ts @@ -0,0 +1,16 @@ +import { ClickablePageObjectType, InputPageObjectType, PageObject, Selector } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; + +export interface SaveDashboardModal { + name: InputPageObjectType; + save: ClickablePageObjectType; + success: PageObject; +} + +export const saveDashboardModal = new TestPage({ + pageObjects: { + name: 'Save dashboard title field', + save: 'Save dashboard button', + success: () => Selector.fromSelector('.alert-success'), + }, +}); diff --git a/packages/grafana-toolkit/src/e2e/pages/datasources/addDataSourcePage.ts b/packages/grafana-toolkit/src/e2e/pages/datasources/addDataSourcePage.ts new file mode 100644 index 00000000000..44d33545649 --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/datasources/addDataSourcePage.ts @@ -0,0 +1,13 @@ +import { ClickablePageObjectType } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; + +export interface AddDataSourcePage { + testDataDB: ClickablePageObjectType; +} + +export const addDataSourcePage = new TestPage({ + url: '/datasources/new', + pageObjects: { + testDataDB: 'TestData DB datasource plugin', + }, +}); diff --git a/packages/grafana-toolkit/src/e2e/pages/datasources/dataSources.ts b/packages/grafana-toolkit/src/e2e/pages/datasources/dataSources.ts new file mode 100644 index 00000000000..76748206a9e --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/datasources/dataSources.ts @@ -0,0 +1,62 @@ +import { Page } from 'puppeteer-core'; + +import { ClickablePageObjectType } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; +import { editDataSourcePage } from './editDataSourcePage'; +import { addDataSourcePage } from './addDataSourcePage'; +import { confirmModal } from '../modals/confirmModal'; + +export interface DataSourcesPage { + testData: ClickablePageObjectType; +} + +export const dataSourcesPageFactory = (testDataSourceName: string) => + new TestPage({ + url: '/datasources', + pageObjects: { + testData: `Data source list item for ${testDataSourceName}`, + }, + }); + +export const addTestDataSourceAndVerify = async (page: Page) => { + // Add TestData DB + const testDataSourceName = `e2e - TestData-${new Date().getTime()}`; + await addDataSourcePage.init(page); + await addDataSourcePage.navigateTo(); + await addDataSourcePage.pageObjects.testDataDB.exists(); + await addDataSourcePage.pageObjects.testDataDB.click(); + + await editDataSourcePage.init(page); + await editDataSourcePage.waitForNavigation(); + await editDataSourcePage.pageObjects.name.enter(testDataSourceName); + await editDataSourcePage.pageObjects.saveAndTest.click(); + await editDataSourcePage.pageObjects.alert.exists(); + await editDataSourcePage.pageObjects.alertMessage.containsText('Data source is working'); + + // Verify that data source is listed + const url = await editDataSourcePage.getUrlWithoutBaseUrl(); + const expectedUrl = url.substring(1, url.length - 1); + const selector = `a[href="${expectedUrl}"]`; + + const dataSourcesPage = dataSourcesPageFactory(testDataSourceName); + await dataSourcesPage.init(page); + await dataSourcesPage.navigateTo(); + await dataSourcesPage.expectSelector({ selector }); + + return testDataSourceName; +}; + +export const cleanUpTestDataSource = async (page: Page, testDataSourceName: string) => { + const dataSourcesPage = dataSourcesPageFactory(testDataSourceName); + await dataSourcesPage.init(page); + await dataSourcesPage.navigateTo(); + await dataSourcesPage.pageObjects.testData.click(); + + await editDataSourcePage.init(page); + await editDataSourcePage.pageObjects.delete.exists(); + await editDataSourcePage.pageObjects.delete.click(); + + await confirmModal.init(page); + await confirmModal.pageObjects.delete.click(); + await confirmModal.pageObjects.success.exists(); +}; diff --git a/packages/grafana-toolkit/src/e2e/pages/datasources/editDataSourcePage.ts b/packages/grafana-toolkit/src/e2e/pages/datasources/editDataSourcePage.ts new file mode 100644 index 00000000000..87b88385015 --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/datasources/editDataSourcePage.ts @@ -0,0 +1,20 @@ +import { ClickablePageObjectType, InputPageObjectType, PageObjectType } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; + +export interface EditDataSourcePage { + name: InputPageObjectType; + delete: ClickablePageObjectType; + saveAndTest: ClickablePageObjectType; + alert: PageObjectType; + alertMessage: PageObjectType; +} + +export const editDataSourcePage = new TestPage({ + pageObjects: { + name: 'Datasource settings page name input field', + delete: 'Delete button', + saveAndTest: 'Save and Test button', + alert: 'Datasource settings page Alert', + alertMessage: 'Datasource settings page Alert message', + }, +}); diff --git a/packages/grafana-toolkit/src/e2e/pages/index.ts b/packages/grafana-toolkit/src/e2e/pages/index.ts index 35fad7284ef..4c01feabf29 100644 --- a/packages/grafana-toolkit/src/e2e/pages/index.ts +++ b/packages/grafana-toolkit/src/e2e/pages/index.ts @@ -1,2 +1,12 @@ export * from './loginPage'; export * from './pluginsPage'; +export * from './dashboards/createDashboardPage'; +export * from './dashboards/dashboardPage'; +export * from './dashboards/dashboardSettingsPage'; +export * from './dashboards/dashboardsPage'; +export * from './dashboards/saveChangesDashboardModal'; +export * from './dashboards/saveDashboardModal'; +export * from './datasources/addDataSourcePage'; +export * from './datasources/dataSources'; +export * from './datasources/editDataSourcePage'; +export * from './modals/confirmModal'; diff --git a/packages/grafana-toolkit/src/e2e/pages/loginPage.ts b/packages/grafana-toolkit/src/e2e/pages/loginPage.ts index c551a92ba1a..2019d08c447 100644 --- a/packages/grafana-toolkit/src/e2e/pages/loginPage.ts +++ b/packages/grafana-toolkit/src/e2e/pages/loginPage.ts @@ -1,11 +1,5 @@ import { TestPage } from '../pageInfo'; -import { - Selector, - InputPageObject, - InputPageObjectType, - ClickablePageObjectType, - ClickablePageObject, -} from '../pageObjects'; +import { ClickablePageObjectType, InputPageObjectType } from '../pageObjects'; export interface LoginPage { username: InputPageObjectType; @@ -16,8 +10,8 @@ export interface LoginPage { export const loginPage = new TestPage({ url: '/login', pageObjects: { - username: new InputPageObject(Selector.fromAriaLabel('Username input field')), - password: new InputPageObject(Selector.fromAriaLabel('Password input field')), - submit: new ClickablePageObject(Selector.fromAriaLabel('Login button')), + username: 'Username input field', + password: 'Password input field', + submit: 'Login button', }, }); diff --git a/packages/grafana-toolkit/src/e2e/pages/modals/confirmModal.ts b/packages/grafana-toolkit/src/e2e/pages/modals/confirmModal.ts new file mode 100644 index 00000000000..f85684693df --- /dev/null +++ b/packages/grafana-toolkit/src/e2e/pages/modals/confirmModal.ts @@ -0,0 +1,14 @@ +import { ClickablePageObjectType, PageObject, Selector } from '../../pageObjects'; +import { TestPage } from '../../pageInfo'; + +export interface ConfirmModal { + delete: ClickablePageObjectType; + success: PageObject; +} + +export const confirmModal = new TestPage({ + pageObjects: { + delete: 'Confirm Modal Danger Button', + success: () => Selector.fromSelector('.alert-success'), + }, +}); diff --git a/packages/grafana-toolkit/src/e2e/scenario.ts b/packages/grafana-toolkit/src/e2e/scenario.ts index 4691ac15609..90b912e3efc 100644 --- a/packages/grafana-toolkit/src/e2e/scenario.ts +++ b/packages/grafana-toolkit/src/e2e/scenario.ts @@ -1,30 +1,69 @@ import { Browser, Page } from 'puppeteer-core'; import { launchBrowser } from './launcher'; import { ensureLoggedIn } from './login'; +import { cleanDashboard, createEmptyDashboardPage } from './pages/dashboards/createDashboardPage'; +import { DashboardPage } from './pages/dashboards/dashboardPage'; +import { TestPage } from './pageInfo'; +import { addTestDataSourceAndVerify, cleanUpTestDataSource } from './pages/datasources/dataSources'; + +export interface ScenarioArguments { + describeName: string; + itName: string; + scenario: (browser: Browser, page: Page, datasourceName?: string, dashboardPage?: TestPage) => void; + skipScenario?: boolean; + createTestDataSource?: boolean; + createTestDashboard?: boolean; +} + +export const e2eScenario = ({ + describeName, + itName, + scenario, + skipScenario = false, + createTestDataSource = false, + createTestDashboard = false, +}: ScenarioArguments) => { + describe(describeName, () => { + if (skipScenario) { + it.skip(itName, async () => { + expect(false).toBe(true); + }); + return; + } -export const e2eScenario = ( - title: string, - testDescription: string, - callback: (browser: Browser, page: Page) => void -) => { - describe(title, () => { let browser: Browser; let page: Page; + let testDataSourceName: string; + let testDashboardTitle: string; + let dashboardPage: TestPage; beforeAll(async () => { browser = await launchBrowser(); page = await browser.newPage(); await ensureLoggedIn(page); + if (createTestDataSource) { + testDataSourceName = await addTestDataSourceAndVerify(page); + } + if (createTestDashboard) { + testDashboardTitle = `e2e - ${new Date().getTime()}`; + dashboardPage = await createEmptyDashboardPage(page, testDashboardTitle); + } }); afterAll(async () => { + if (testDataSourceName) { + await cleanUpTestDataSource(page, testDataSourceName); + } + if (testDashboardTitle && dashboardPage) { + await cleanDashboard(page, testDashboardTitle); + } if (browser) { await browser.close(); } }); - it(testDescription, async () => { - await callback(browser, page); + it(itName, async () => { + await scenario(browser, page, testDataSourceName, dashboardPage); }); }); }; diff --git a/packages/grafana-toolkit/src/plugins/e2e/commonPluginTests.ts b/packages/grafana-toolkit/src/plugins/e2e/commonPluginTests.ts index 9cb89c86cec..3e76e08cc92 100644 --- a/packages/grafana-toolkit/src/plugins/e2e/commonPluginTests.ts +++ b/packages/grafana-toolkit/src/plugins/e2e/commonPluginTests.ts @@ -1,6 +1,6 @@ import { Browser, Page } from 'puppeteer-core'; -import { e2eScenario, takeScreenShot, pages } from '@grafana/toolkit/src/e2e'; +import { e2eScenario, pages, takeScreenShot } from '@grafana/toolkit/src/e2e'; import { getEndToEndSettings } from '@grafana/toolkit/src/plugins'; // **************************************************************** @@ -11,14 +11,18 @@ const sleep = (milliseconds: number) => { return new Promise(resolve => setTimeout(resolve, milliseconds)); }; -e2eScenario('Common Plugin Test', 'should pass', async (browser: Browser, page: Page) => { - const settings = getEndToEndSettings(); - const pluginPage = pages.getPluginPage(settings.plugin.id); - await pluginPage.init(page); - await pluginPage.navigateTo(); - // TODO: find a better way to avoid the 'loading' page - await sleep(500); +e2eScenario({ + describeName: 'Common Plugin Test', + itName: 'should pass', + scenario: async (browser: Browser, page: Page) => { + const settings = getEndToEndSettings(); + const pluginPage = pages.getPluginPage(settings.plugin.id); + await pluginPage.init(page); + await pluginPage.navigateTo(); + // TODO: find a better way to avoid the 'loading' page + await sleep(500); - const fileName = 'plugin-page'; - await takeScreenShot(page, fileName); + const fileName = 'plugin-page'; + await takeScreenShot(page, fileName); + }, }); diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx index f5df3d090d7..2239ac740fc 100644 --- a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx +++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx @@ -1,6 +1,7 @@ -import React, { useContext, MouseEvent } from 'react'; +import React, { MouseEvent, useContext } from 'react'; import { CallToActionCard, LinkButton, ThemeContext } from '@grafana/ui'; import { css } from 'emotion'; + export interface Props { title: string; buttonIcon: string; @@ -72,7 +73,14 @@ const EmptyListCTA: React.FunctionComponent = ({ : ''; const ctaElement = ( - + {buttonTitle} ); diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 771b0207693..346ca903edb 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -1,22 +1,18 @@ // Libaries import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; - // Utils & Services import { appEvents } from 'app/core/app_events'; import { PlaylistSrv } from 'app/features/playlist/playlist_srv'; - // Components import { DashNavButton } from './DashNavButton'; import { DashNavTimeControls } from './DashNavTimeControls'; import { Tooltip } from '@grafana/ui'; - // State import { updateLocation } from 'app/core/actions'; - // Types import { DashboardModel } from '../../state'; -import { StoreState, CoreEvents } from 'app/types'; +import { CoreEvents, StoreState } from 'app/types'; export interface OwnProps { dashboard: DashboardModel; @@ -160,7 +156,11 @@ export class DashNav extends PureComponent { return (
- diff --git a/public/app/features/dashboard/components/DashboardSettings/template.html b/public/app/features/dashboard/components/DashboardSettings/template.html index d94632a8253..b0275cee1ec 100644 --- a/public/app/features/dashboard/components/DashboardSettings/template.html +++ b/public/app/features/dashboard/components/DashboardSettings/template.html @@ -1,14 +1,24 @@