From f9fa9288e055557e2c8c5023cf09b546affb76c5 Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Fri, 1 Mar 2024 16:14:56 -0700 Subject: [PATCH] feedback from group review --- .github/workflows/playwright.yml | 31 ++++++++++++++++ e2e/generateTestData.js | 30 ++++++++++----- e2e/pages/analysisPage.ts | 12 ++++-- e2e/pages/analysisResultsPage.ts | 2 +- e2e/pages/basePage.ts | 27 ++++++++------ e2e/pages/explorePage.ts | 11 ++++++ e2e/tests/analysis.spec.ts | 61 +++++++++++++++++++++++++++---- e2e/tests/exploreDatasets.spec.ts | 37 +++++++++++++++++++ 8 files changed, 178 insertions(+), 33 deletions(-) create mode 100644 .github/workflows/playwright.yml create mode 100644 e2e/pages/explorePage.ts create mode 100644 e2e/tests/exploreDatasets.spec.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 000000000..a9d6b0841 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,31 @@ +name: Playwright Tests +on: + push: + branches: [ main, master, develop ] + pull_request: + branches: [ main, master, develop ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - uses: actions/setup-node@v3 + with: + node-version: 16 + - name: Install yarn + run: npm install -g yarn + - name: Run Veda setup + run: ./.veda/setup + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Playwright tests + run: yarn test:e2e + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: /veda/ui/playwright-report/ + retention-days: 30 \ No newline at end of file diff --git a/e2e/generateTestData.js b/e2e/generateTestData.js index 89c83ca9a..d9c05eb5c 100644 --- a/e2e/generateTestData.js +++ b/e2e/generateTestData.js @@ -6,27 +6,37 @@ const fg = require('fast-glob'); const catalogPaths = fg.globSync('**/datasets/*.mdx'); const storyPaths = fg.globSync('**/stories/*.mdx'); const catalogNames = []; +const datasetIds = []; const storyNames = []; for (const catalog of catalogPaths) { const catalogData = matter.read(catalog).data; catalogNames.push(catalogData['name']); + datasetIds.push(catalogData['id']); } for (const story of storyPaths) { const storyData = matter.read(story).data; - storyNames.push(storyData['name']) + storyNames.push(storyData['name']); } const testDataJson = { - "catalogs": catalogNames, - "stories": storyNames -} + catalogs: catalogNames, + datasetIds: datasetIds, + stories: storyNames +}; -fs.writeFile(path.join(__dirname, 'playwrightTestData.json'), JSON.stringify(testDataJson), err => { - if (err) { - console.error(err); - } else { - console.info('new test data file generated') +fs.writeFile( + path.join(__dirname, 'playwrightTestData.json'), + JSON.stringify(testDataJson), + (err) => { + if (err) { + // eslint-disable-next-line no-console + console.error(err); + throw err; + } else { + // eslint-disable-next-line no-console + console.info('new test data file generated'); + } } -}); \ No newline at end of file +); \ No newline at end of file diff --git a/e2e/pages/analysisPage.ts b/e2e/pages/analysisPage.ts index 36f267a1e..f1a658bf1 100644 --- a/e2e/pages/analysisPage.ts +++ b/e2e/pages/analysisPage.ts @@ -2,22 +2,26 @@ import { Locator, Page, test } from '@playwright/test'; export default class AnalysisPage { readonly page: Page; + readonly selectDatasetsMessage: Locator; readonly mainContent: Locator; readonly header: Locator; readonly mapboxCanvas: Locator; readonly generateAnalysisButton: Locator; - readonly datasetOptions: Locator; readonly datasetCheckbox: Locator; + readonly moreOptionsButton: Locator; + readonly northAmericaOption: Locator; constructor(page: Page) { this.page = page; + this.selectDatasetsMessage = this.page.getByText(/To select datasets, please define an area and a date first/i); this.mainContent = this.page.getByRole('main'); this.header = this.mainContent.getByRole('heading', {level: 1, name: /analysis/i }); this.mapboxCanvas = this.page.getByLabel('Map', { exact: true }); this.generateAnalysisButton = this.page.getByRole('link', { name: /Generate analysis/i }); - this.datasetOptions = this.page.getByTestId('datasetOptions'); - this.datasetCheckbox = this.datasetOptions.getByRole('checkbox'); + this.datasetCheckbox = this.page.locator('label').filter({ hasText: /From:/i }) + this.moreOptionsButton = this.page.getByRole('button', {name: /more options/i }); + this.northAmericaOption = this.page.getByRole('button', {name: /north america/i }); } async drawPolygon (polygonCorners: number[][]) { @@ -49,7 +53,7 @@ export default class AnalysisPage { async clickDatasetOption (index: number) { test.step(`clicking dataset number ${index}`, async () => { - this.datasetCheckbox.nth(index).locator('..').click(); + this.datasetCheckbox.nth(index).click(); }) } } \ No newline at end of file diff --git a/e2e/pages/analysisResultsPage.ts b/e2e/pages/analysisResultsPage.ts index b018735d7..076b26ba8 100644 --- a/e2e/pages/analysisResultsPage.ts +++ b/e2e/pages/analysisResultsPage.ts @@ -7,7 +7,7 @@ export default class AnalysisResultsPage { constructor(page: Page) { this.page = page; - this.analysisCards = this.page.getByTestId('analysisCards'); + this.analysisCards = this.page.getByRole('article'); } } \ No newline at end of file diff --git a/e2e/pages/basePage.ts b/e2e/pages/basePage.ts index b2dde30a1..a2e86a5f9 100644 --- a/e2e/pages/basePage.ts +++ b/e2e/pages/basePage.ts @@ -2,22 +2,24 @@ import { test as base } from '@playwright/test'; import AboutPage from './aboutPage'; import AnalysisPage from './analysisPage'; import AnalysisResultsPage from './analysisResultsPage'; -import HomePage from './homePage'; -import FooterComponent from './footerComponent'; -import HeaderComponent from './headerComponent'; import CatalogPage from './catalogPage'; import DatasetPage from './datasetPage'; +import ExplorePage from './explorePage'; +import FooterComponent from './footerComponent'; +import HeaderComponent from './headerComponent'; +import HomePage from './homePage'; import StoryPage from './storyPage'; export const test = base.extend<{ aboutPage: AboutPage; analysisPage: AnalysisPage; analysisResultsPage: AnalysisResultsPage; + catalogPage: CatalogPage; + datasetPage: DatasetPage; + explorePage: ExplorePage; footerComponent: FooterComponent; headerComponent: HeaderComponent; homePage: HomePage; - catalogPage: CatalogPage; - datasetPage: DatasetPage; storyPage: StoryPage }> ({ aboutPage: async ({page}, use) => { @@ -35,17 +37,20 @@ export const test = base.extend<{ datasetPage: async ({page}, use) => { await use(new DatasetPage(page)); }, - homePage: async ({page}, use) => { - await use(new HomePage(page)); + explorePage: async ({page}, use) => { + await use(new ExplorePage(page)); }, - storyPage: async ({page}, use) => { - await use(new StoryPage(page)); + footerComponent: async ({page}, use) => { + await use(new FooterComponent(page)); }, headerComponent: async ({page}, use) => { await use(new HeaderComponent(page)); }, - footerComponent: async ({page}, use) => { - await use(new FooterComponent(page)); + homePage: async ({page}, use) => { + await use(new HomePage(page)); + }, + storyPage: async ({page}, use) => { + await use(new StoryPage(page)); }, }); diff --git a/e2e/pages/explorePage.ts b/e2e/pages/explorePage.ts new file mode 100644 index 000000000..303c6d295 --- /dev/null +++ b/e2e/pages/explorePage.ts @@ -0,0 +1,11 @@ +import { Locator, Page } from '@playwright/test'; + +export default class ExplorePage { + readonly page: Page; + readonly layersHeading: Locator; + + constructor(page: Page) { + this.page = page; + this.layersHeading = this.page.getByRole('heading', { name: 'Layers' }); + } +} \ No newline at end of file diff --git a/e2e/tests/analysis.spec.ts b/e2e/tests/analysis.spec.ts index 988c646fe..6699f4203 100644 --- a/e2e/tests/analysis.spec.ts +++ b/e2e/tests/analysis.spec.ts @@ -1,14 +1,14 @@ import { test, expect } from '../pages/basePage'; -test('load /analysis route', async ({ +test('generate analysis with polygon', async ({ page, analysisPage, analysisResultsPage, }) => { let pageErrorCalled = false; - // Log all uncaught errors to the terminal + // Log all uncaught errors to the terminal to be visible in trace page.on('pageerror', exception => { - console.log(`Uncaught exception: "${exception}"`); + console.log(`Uncaught exception: "${JSON.stringify(exception)}"`); pageErrorCalled = true; }); @@ -22,13 +22,58 @@ test('load /analysis route', async ({ const box = await analysisPage.mapboxCanvas.boundingBox(); // using Non-null Assertion because we know the mapbox is visible, therefore box is not null - const firstCorner = [box!.width / 4, box!.height / 4]; - const secondCorner = [box!.width / 3, box!.height / 4]; - const thirdCorner = [box!.width / 4, box!.height / 3]; + const firstCorner = [box!.width * 0.2, box!.height * 0.2]; + const secondCorner = [box!.width * 0.8, box!.height * 0.2]; + const thirdCorner = [box!.width * 0.8, box!.height * 0.8]; + const fourthCorner = [box!.width * 0.2, box!.height * 0.8]; + await analysisPage.mapboxCanvas.click(); - await analysisPage.drawPolygon([firstCorner, secondCorner, thirdCorner]) + await analysisPage.drawPolygon([firstCorner, secondCorner, thirdCorner, fourthCorner]) + + await expect(analysisPage.selectDatasetsMessage).toBeHidden(); + await expect(page.getByText(/loading/i)).toBeHidden(); + await analysisPage.clickDatasetOption(1); + + const searchResponsePromise = page.waitForResponse(/\/search/i); + await analysisPage.generateAnalysisButton.click(); + + + const searchResponse = await searchResponsePromise; + expect(searchResponse.ok(), 'request to GET /search should be successful').toBeTruthy(); + + await expect(analysisResultsPage.analysisCards.first(), 'at least one analysis results is visible' ).toBeVisible(); + + // scroll page to bottom + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false); + +}); + + +test('generate analysis with region select', async ({ + page, + analysisPage, + analysisResultsPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal to be visible in trace + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${JSON.stringify(exception)}"`); + pageErrorCalled = true; + }); + + const mapboxResponsePromise = page.waitForResponse(/api\.mapbox.com\/v4\/mapbox\.mapbox-streets-v8/i); + await page.goto('/analysis'); + await expect(analysisPage.header, `analysis page should load`).toBeVisible(); + const mapboxResponse = await mapboxResponsePromise; + expect(mapboxResponse.ok(), 'mapbox request should be successful').toBeTruthy(); + await expect(analysisPage.mapboxCanvas, 'mapbox canvas should be visible').toBeVisible(); + + await analysisPage.moreOptionsButton.click(); + await analysisPage.northAmericaOption.click(); await analysisPage.clickDatasetOption(1); @@ -41,4 +86,6 @@ test('load /analysis route', async ({ await expect(analysisResultsPage.analysisCards.first(), 'at least one analysis results is visible' ).toBeVisible(); + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false); + }); \ No newline at end of file diff --git a/e2e/tests/exploreDatasets.spec.ts b/e2e/tests/exploreDatasets.spec.ts new file mode 100644 index 000000000..f544f07c2 --- /dev/null +++ b/e2e/tests/exploreDatasets.spec.ts @@ -0,0 +1,37 @@ +import fs from 'fs'; +import { test, expect } from '../pages/basePage'; + +const datasetIds = JSON.parse(fs.readFileSync('e2e/playwrightTestData.json', 'utf8')).datasetIds; + +test.describe('explore dataset', () => { + for (const dataset of datasetIds) { + test(`${dataset} explore page functions`, async({ + page, + explorePage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal to be visible in trace + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${JSON.stringify(exception)}"`); + pageErrorCalled = true; + }); + + //mosaic isn't hit on all datasets + const collectionsResponsePromise = page.waitForResponse(response => + response.url().includes('collections') && response.status() === 200 + ); + + await page.goto(`data-catalog/${dataset}/explore`); + await expect(explorePage.layersHeading).toBeVisible(); + + const mosaicResponse = await collectionsResponsePromise; + expect(mosaicResponse.ok(), 'mapbox request should be successful').toBeTruthy(); + + // scroll page to bottom + await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); + + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false); + }); + } + +}); \ No newline at end of file