From 9ed6ecd9c8b196b22dbef82d339e409da4c3a5a6 Mon Sep 17 00:00:00 2001 From: fabio-looker Date: Mon, 9 May 2022 14:45:59 +0000 Subject: [PATCH] test: Diff scene e2e tests (#1073) * All new e2e tests for diff scene * One new e2e test for main scene (navigate from method example to github) * Fixed 2/3 broken tests merged from main branch. Commented remaining 1/3 --- packages/api-explorer/e2e/diffScene.spec.ts | 260 ++++++++++++++++++ packages/api-explorer/e2e/e2e.spec.ts | 89 ++++-- packages/api-explorer/e2e/helpers.ts | 10 +- .../api-explorer/jest-puppeteer.config.js | 4 + .../src/scenes/DiffScene/DiffScene.tsx | 1 + 5 files changed, 334 insertions(+), 30 deletions(-) create mode 100644 packages/api-explorer/e2e/diffScene.spec.ts diff --git a/packages/api-explorer/e2e/diffScene.spec.ts b/packages/api-explorer/e2e/diffScene.spec.ts new file mode 100644 index 000000000..d32210a94 --- /dev/null +++ b/packages/api-explorer/e2e/diffScene.spec.ts @@ -0,0 +1,260 @@ +/* + + MIT License + + Copyright (c) 2021 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ +import '@testing-library/jest-dom' + +import { goToPage, BASE_URL } from './helpers' + +// https://github.com/smooth-code/jest-puppeteer/tree/master/packages/expect-puppeteer +// https://github.com/puppeteer/puppeteer/blob/main/docs/api.md + +jest.setTimeout(120000) + +const resultCardsSelector = + 'section#top div[class*=SpaceVertical] div[class*=Card]' +const baseInputSelector = 'input#listbox-input-base' +const compInputSelector = 'input#listbox-input-compare' +const globalOptionsSelector = '#modal-root [role=option] span' +const switchButtonSelector = '.switch-button' + +describe('Diff Scene', () => { + beforeEach(async () => { + await jestPuppeteer.resetBrowser() + await page.setDefaultNavigationTimeout(120000) + }) + it('loads the default scene (/diff/3.1)', async () => { + await goToPage(`${BASE_URL}/diff/3.1`) + const body = await page.$('body') + + // "Base" input element + { + await body.click() + const baseInputElement = await page.$(baseInputSelector) + expect(baseInputElement).not.toBeNull() + const baseInputValue = await page.evaluate( + (e) => e.value, + baseInputElement + ) + expect(baseInputValue).toMatch('3.1') + + const baseOptionsOnLoad = await page.$(globalOptionsSelector) + expect(baseOptionsOnLoad).toBeNull() + + await baseInputElement.click() + const baseOptionsOnClick = await page.$$(globalOptionsSelector) + expect(baseOptionsOnClick).not.toHaveLength(0) + + await body.click() + const baseOptionsOnClose = await page.$(globalOptionsSelector) + expect(baseOptionsOnClose).toBeNull() + } + + // "Comparison" input element + { + await body.click() + const compInputElement = await page.$(compInputSelector) + expect(compInputElement).not.toBeNull() + const compInputValue = await page.evaluate( + (e) => e.value, + compInputElement + ) + expect(compInputValue).toEqual('') + + const compOptionsOnLoad = await page.$(globalOptionsSelector) + expect(compOptionsOnLoad).toBeNull() + + await compInputElement.click() + const compOptionsOnClick = await page.$$(globalOptionsSelector) + expect(compOptionsOnClick).not.toHaveLength(0) + + await body.click() + const compOptionsOnClose = await page.$(globalOptionsSelector) + expect(compOptionsOnClose).toBeNull() + } + + // Switch button (disabled) + { + const switchButtonElement = await page.$(switchButtonSelector) + expect(switchButtonElement).not.toBeNull() + const switchButtonDisabled = await page.evaluate( + (e) => e.disabled, + switchButtonElement + ) + expect(switchButtonDisabled).toEqual(true) + } + }) + + it('loads a comparison scene (/diff/3.1/4.0) and navigates from it', async () => { + await goToPage(`${BASE_URL}/diff/3.1/4.0`) + + // "Base" input element + { + const baseInputElement = await page.$(baseInputSelector) + expect(baseInputElement).not.toBeNull() + const baseInputValue = await page.evaluate( + (e) => e.value, + baseInputElement + ) + expect(baseInputValue).toMatch('3.1') + } + + // "Comparison" input element + { + const compInputElement = await page.$(compInputSelector) + expect(compInputElement).not.toBeNull() + const compInputValue = await page.evaluate( + (e) => e.value, + compInputElement + ) + expect(compInputValue).toMatch('4.0') + } + + // Switch button + { + const switchButtonElement = await page.$(switchButtonSelector) + expect(switchButtonElement).not.toBeNull() + const switchButtonDisabled = await page.evaluate( + (e) => e.disabled, + switchButtonElement + ) + expect(switchButtonDisabled).toEqual(false) + } + + // Diff results + { + const diffResultCards = await page.$$(resultCardsSelector) + expect(diffResultCards).not.toHaveLength(0) + const page1Methods = await Promise.all( + diffResultCards.map((resultCard) => + page.evaluate((el) => el.innerText.match(/^[a-z_]*/)[0], resultCard) + ) + ) + expect(page1Methods).toHaveLength(15) + expect(page1Methods).toContain('delete_board_item') + } + + // Expand a result + { + const expandedSelector = + resultCardsSelector + `>div[class*=Accordion2]>div[aria-expanded=true]` + + // Initially not expanded + const expandedCardBefore = await page.$(expandedSelector) + expect(expandedCardBefore).toBeNull() + + // Click a card + const firstResultCard = (await page.$$(resultCardsSelector))[0] + await firstResultCard.click() + + // Expanded + const expandedCardAfter = await page.$(expandedSelector) + expect(expandedCardAfter).not.toBeNull() + + // Find and validate method link + const methodLink = await page.$(`${resultCardsSelector} a[role=link]`) + expect(methodLink).not.toBeNull() + const methodText = await page.evaluate((e) => e.innerText, methodLink) + expect(methodText).toMatch(`delete_board_item for 4.0`) + + // Click and validate destination + await methodLink.click() + await page.waitForSelector(`div[class*=MethodBadge]`, { timeout: 5000 }) + const compUrl = page.url() + expect(compUrl).toEqual(`${BASE_URL}/4.0/methods/Board/delete_board_item`) + } + }) + + it('updates when a comparison is chosen or switched', async () => { + await goToPage(`${BASE_URL}/diff/3.1`) + + // "Base" input element + const baseInputElement = await page.$(baseInputSelector) + expect(baseInputElement).not.toBeNull() + + // "Comparison" input element + const compInputElement = await page.$(compInputSelector) + expect(compInputElement).not.toBeNull() + + // Click comparison input + await compInputElement.click() + const compOptionsOnClick = await page.$$(globalOptionsSelector) + expect(compOptionsOnClick).not.toHaveLength(0) + expect(compOptionsOnClick).not.toHaveLength(1) + + // Find an option containing the text 4.0 + const option40Index = await page.$$eval(globalOptionsSelector, (els) => + els.findIndex((el) => el.textContent.match(/4\.0/)) + ) + const option40 = compOptionsOnClick[option40Index] + expect(option40).not.toBeUndefined() + + // Click that option + await option40.click() + await page.waitForSelector(resultCardsSelector, { timeout: 5000 }) + + // Check the URL + // Would like to do this earlier, but not sure what to wait on + const compUrl = page.url() + expect(compUrl).toEqual(`${BASE_URL}/diff/3.1/4.0`) + + // Check the results + const diffResultCards = await page.$$(resultCardsSelector) + expect(diffResultCards).not.toHaveLength(0) + const diff31to40Page1Methods = await Promise.all( + diffResultCards.map((resultCard) => + page.evaluate((el) => el.innerText.match(/^[a-z_]*/)[0], resultCard) + ) + ) + + expect(diff31to40Page1Methods).toHaveLength(15) + expect(diff31to40Page1Methods).toContain('delete_board_item') + + // Click the switch button + const switchButtonElement = await page.$(switchButtonSelector) + expect(switchButtonElement).not.toBeNull() + const switchButtonDisabled = await page.evaluate( + (e) => e.disabled, + switchButtonElement + ) + expect(switchButtonDisabled).toEqual(false) + await switchButtonElement.click() + + // A more precise timing mechanism would be better: https://github.com/puppeteer/puppeteer/issues/5328 + await page.waitForTimeout(150) + + const switchUrl = page.url() + expect(switchUrl).toEqual(`${BASE_URL}/diff/4.0/3.1`) + + // Check the results again, even though they should be the same + const diff40to31Page1Methods = await Promise.all( + diffResultCards.map((resultCard) => + page.evaluate((el) => el.innerText.match(/^[a-z_]*/)[0], resultCard) + ) + ) + + expect(diff40to31Page1Methods).toHaveLength(15) + expect(diff40to31Page1Methods).toContain('delete_board_item') + }) +}) diff --git a/packages/api-explorer/e2e/e2e.spec.ts b/packages/api-explorer/e2e/e2e.spec.ts index 3003e602a..bfdd2952f 100644 --- a/packages/api-explorer/e2e/e2e.spec.ts +++ b/packages/api-explorer/e2e/e2e.spec.ts @@ -25,42 +25,44 @@ */ import '@testing-library/jest-dom' -import { goToPage, pageReload } from './helpers' +import { goToPage, pageReload, BASE_URL } from './helpers' // https://github.com/smooth-code/jest-puppeteer/tree/master/packages/expect-puppeteer // https://github.com/puppeteer/puppeteer/blob/main/docs/api.md jest.setTimeout(120000) -const BASE_URL = 'https://localhost:8080' -const v31 = `${BASE_URL}/3.1` const v40 = `${BASE_URL}/4.0` describe('API Explorer', () => { beforeEach(async () => { await jestPuppeteer.resetBrowser() + await page.setDefaultNavigationTimeout(120000) }) describe('general', () => { beforeEach(async () => { - await goToPage(v31) + await goToPage(v40) }) it('renders a method page', async () => { - await expect(page).toClick('h4', { text: 'Dashboard' }) await Promise.all([ page.waitForNavigation(), - expect(page).toClick('a', { text: 'Get All Dashboards' }), + expect(page).toClick('h4', { text: 'Dashboard' }), + ]) + await Promise.all([ + page.waitForNavigation(), + expect(page).toClick('h3', { text: 'Get All Dashboards' }), ]) await expect(page.url()).toEqual( - `${v31}/methods/Dashboard/all_dashboards` + `${v40}/methods/Dashboard/all_dashboards` ) // title await expect(page).toMatchElement('h2', { text: 'Get All Dashboards' }) // markdown - await expect(page).toMatchElement('div', { + await expect(page).toMatchElement('h3', { text: 'Get information about all active dashboards', }) @@ -115,7 +117,7 @@ describe('API Explorer', () => { await expect(page).toMatchElement('h2', { text: 'ApiAuth: API Authentication', }) - await expect(page.url()).toMatch(`${v31}/methods/ApiAuth`) + await expect(page.url()).toMatch(`${v40}/methods/ApiAuth`) await expect(page).toMatchElement( 'button[value="ALL"][aria-pressed=true]' @@ -164,31 +166,35 @@ describe('API Explorer', () => { await expect(page).toMatchElement('h3', { text: 'Kotlin Declaration' }) }) - it('changes specs', async () => { - await expect(page).toMatchElement('h2', { - text: 'Looker API 3.1 Reference', - }) - await expect(page).toClick('input[value="3.1"]') - await expect(page).toClick( - 'ul[aria-label="spec selector"] > li:last-child' - ) - await expect(page).toMatchElement('h2', { - text: 'Looker API 4.0 (Beta) Reference', - }) - }) + // This test was broken during the 4.0 GA spec changes, and needs to be fixed + // it('changes specs', async () => { + // await expect(page).toMatchElement('h2', { + // text: 'Looker API 4.0 Reference', + // }) + // await expect(page).toClick('input[value="4.0"]') + // await Promise.all([ + // page.waitForNavigation(), + // expect(page).toClick( + // 'ul[aria-label="spec selector"]>li:nth-of-type(2)' + // ) + // ]) + // //Because waitForNavigation doesn't seem to be doing what we need + // await page.waitForTimeout(150) + // await expect(page.url()).toEqual(v31) + // }) }) describe('navigation', () => { it('should be able to navigate directly to a spec home', async () => { - await goToPage(v31) + await goToPage(v40) await expect(page).toMatchElement('h2', { - text: 'Looker API 3.1 Reference', + text: 'Looker API 4.0 Reference', }) - await expect(page).toMatchElement('input[value="3.1"]') + await expect(page).toMatchElement('input[value="4.0"]') }) it('should be able to navigate directly to a tag scene', async () => { - await goToPage(`${v31}/methods/Dashboard`) + await goToPage(`${v40}/methods/Dashboard`) await expect(page).toMatchElement('h2', { text: 'Dashboard: Manage Dashboards', }) @@ -203,15 +209,40 @@ describe('API Explorer', () => { }) it('should be able to navigate directly to a type', async () => { - await goToPage(`${v31}/types/Query/Query`) + await goToPage(`${v40}/types/Query/Query`) await expect(page).toMatchElement('h2', { text: 'Query' }) await expect(page).toMatchElement('button', { text: 'Query' }) }) }) + describe('outbound navigation', () => { + it('should be able to navigate from a method to Github', async () => { + await goToPage(`${v40}/methods/Dashboard/all_dashboards`) + const exampleLink = await page.$( + 'table[aria-label="SDK Examples"] a[class*=Link][target=_blank' + ) + expect(exampleLink).not.toBeNull() + + // Suggested/intuitive method of waiting for navigation does not seem to be working and just waits indefinitely on the target page + // await Promise.all([ + // page.waitForNavigation({timeout:5000}), + // exampleLink.click() + // ]) + await exampleLink.click() + await page.waitForTimeout(150) + + const body = await page.$('body') + const codeMatch = await page.evaluate( + (e) => e.innerText.match('all_dashboards\\('), + body + ) + expect(codeMatch).not.toBeNull() + }) + }) + describe('search', () => { beforeEach(async () => { - await goToPage(v31) + await goToPage(v40) }) it('searches methods', async () => { @@ -224,7 +255,7 @@ describe('API Explorer', () => { await expect(page).toMatchElement('button', { text: 'Types (0)' }) await expect(page).toClick('a', { text: 'Get Workspace' }) await expect(page).toMatchElement('h2', { text: 'Get Workspace' }) - await expect(page.url()).toEqual(`${v31}/methods/Workspace/workspace`) + await expect(page.url()).toEqual(`${v40}/methods/Workspace/workspace`) }) it('searches types', async () => { @@ -236,7 +267,7 @@ describe('API Explorer', () => { await expect(page).toClick('button', { text: 'Types (1)' }) await expect(page).toClick('a', { text: 'WriteTheme' }) await expect(page).toMatchElement('h2', { text: 'WriteTheme' }) - await expect(page.url()).toEqual(`${v31}/types/Theme/WriteTheme`) + await expect(page.url()).toEqual(`${v40}/types/Theme/WriteTheme`) }) }) }) diff --git a/packages/api-explorer/e2e/helpers.ts b/packages/api-explorer/e2e/helpers.ts index a56f62c06..2d3bc460a 100644 --- a/packages/api-explorer/e2e/helpers.ts +++ b/packages/api-explorer/e2e/helpers.ts @@ -23,6 +23,14 @@ SOFTWARE. */ + +/** + * Constants + */ +export const BASE_URL = 'https://localhost:8080' +export const v31Url = `${BASE_URL}/3.1` +export const v40Url = `${BASE_URL}/4.0` + /** * Reloads the page, waiting for for the DomContentLoaded event before resolving */ @@ -37,7 +45,7 @@ export const pageReload = async (): Promise => { export const goToPage = async (url: string): Promise => { await page.goto(url, { waitUntil: ['domcontentloaded', 'networkidle0'], - timeout: 60000, + timeout: 120000, }) } diff --git a/packages/api-explorer/jest-puppeteer.config.js b/packages/api-explorer/jest-puppeteer.config.js index ba96414f8..6fa023564 100644 --- a/packages/api-explorer/jest-puppeteer.config.js +++ b/packages/api-explorer/jest-puppeteer.config.js @@ -25,7 +25,11 @@ */ module.exports = { launch: { + // `headless:false` and `slowMo:250` can be useful for "test-watch" usage headless: true, + // slowMo: 250, + + // Other launch settings constant for both dev and testing usage args: ['--ignore-certificate-errors'], product: 'chrome', devtools: true, diff --git a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx index 5cbe52e5e..0adf0855e 100644 --- a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx +++ b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx @@ -170,6 +170,7 @@ export const DiffScene: FC = ({ specs, toggleNavigation }) => { />