From 1d34a0ff4aca9693fbe35bfe506fd8889a84560e Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Thu, 1 Feb 2024 13:21:55 -0700 Subject: [PATCH 1/9] add playwright --- .gitignore | 12 +++ e2e/README.md | 35 +++++++ e2e/generateTestData.js | 32 ++++++ e2e/pages/aboutPage.ts | 11 ++ e2e/pages/basePage.ts | 42 ++++++++ e2e/pages/catalogPage.ts | 14 +++ e2e/pages/datasetPage.ts | 18 ++++ e2e/pages/footerComponent.ts | 13 +++ e2e/pages/headerComponent.ts | 23 ++++ e2e/pages/homePage.ts | 14 +++ e2e/pages/storyPage.ts | 14 +++ e2e/tests/catalog.spec.ts | 27 +++++ e2e/tests/catalogRouting.spec.ts | 35 +++++++ e2e/tests/stories.spec.ts | 30 ++++++ e2e/tests/storiesRouting.spec.ts | 30 ++++++ package.json | 8 +- playwright.config.ts | 58 ++++++++++ yarn.lock | 175 ++++++++++++++++++++++++++++++- 18 files changed, 588 insertions(+), 3 deletions(-) create mode 100644 e2e/README.md create mode 100644 e2e/generateTestData.js create mode 100644 e2e/pages/aboutPage.ts create mode 100644 e2e/pages/basePage.ts create mode 100644 e2e/pages/catalogPage.ts create mode 100644 e2e/pages/datasetPage.ts create mode 100644 e2e/pages/footerComponent.ts create mode 100644 e2e/pages/headerComponent.ts create mode 100644 e2e/pages/homePage.ts create mode 100644 e2e/pages/storyPage.ts create mode 100644 e2e/tests/catalog.spec.ts create mode 100644 e2e/tests/catalogRouting.spec.ts create mode 100644 e2e/tests/stories.spec.ts create mode 100644 e2e/tests/storiesRouting.spec.ts create mode 100644 playwright.config.ts diff --git a/.gitignore b/.gitignore index 16647576d..1fcc568bd 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,15 @@ dist Makefile .hypothesis + +################################################ +# Playwright Test Outputs +# +# Files generated by running Playwright tests +################################################ + +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/e2e/playwrightTestData.json \ No newline at end of file diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 000000000..075171b81 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,35 @@ +# Playwright E2E Testing + +This testing suite is for End to End testing the VEDA website via the UI using Playwright. It works by serving a local version of the site using yarn serve and performing UI checks against that locally hosted site. The suite is designed to generate a `playwrightTestData.json` that contains the list of all Catalog and Story names. This is done by parsing the `name` field in the `*.mdx` files of the `/datasets` and `/stories` directories. + +## Running the test suite + +The test suite can be run via the `yarn test:e2e` script. There is a `prtest:e2e` script that will generate a new playwrightTestData.json before beginning the actual playwright test run. This allows for new stories or catalogs to be added without updating the test suite. + +## Directory Structure + +The end to end tests are organized in the `/e2e` directory. The tests have been written following a [Page Object Model](https://martinfowler.com/bliki/PageObject.html) pattern. +Supporting files within the repo are in the following structure: + +```text +/e2e + │ + │─── README.md + │─── playwright.config.ts - imports our global setup, defines preferred browsers, & number of retries + │─── generateTestData.js - parses mdx files and creates a json based on their metadata + └─── /pages + │ └─── basePage.ts - imports all seeded data and PageObjects into our `test` object. + │ │ + │ └─── [PAGENAME]Page.ts - The page objects. UI elements interacted with on a page are defined once to keep tests DRY and minimize test changes if layout changes. + └─── tests - our actual tests +``` + +## Updating Tests + +If the layout of a page changes, then the tests may no longer be able to interact with locators. These locators are defined in the Page Objects defined in `/e2e/pages`. The Playwright framework provides multiple ways to choose elements to interact with. The recomended ones are defined in the [Playwright documentation](https://playwright.dev/docs/locators#quick-guide). + +Any new pages will need to have new page objects created and then imported into the `basePage.ts` file following th format of existing pages. This allows all tests to reference the page. + +## Output + +Playwright will generate an html report with test results. This report will show all tests that were run, and will allow a user to view the results of any failed tests. diff --git a/e2e/generateTestData.js b/e2e/generateTestData.js new file mode 100644 index 000000000..89c83ca9a --- /dev/null +++ b/e2e/generateTestData.js @@ -0,0 +1,32 @@ +const fs = require('fs'); +const path = require('path'); +const matter = require('gray-matter'); +const fg = require('fast-glob'); + +const catalogPaths = fg.globSync('**/datasets/*.mdx'); +const storyPaths = fg.globSync('**/stories/*.mdx'); +const catalogNames = []; +const storyNames = []; + +for (const catalog of catalogPaths) { + const catalogData = matter.read(catalog).data; + catalogNames.push(catalogData['name']); +} + +for (const story of storyPaths) { + const storyData = matter.read(story).data; + storyNames.push(storyData['name']) +} + +const testDataJson = { + "catalogs": catalogNames, + "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') + } +}); \ No newline at end of file diff --git a/e2e/pages/aboutPage.ts b/e2e/pages/aboutPage.ts new file mode 100644 index 000000000..a9e84eac3 --- /dev/null +++ b/e2e/pages/aboutPage.ts @@ -0,0 +1,11 @@ +import { Locator, Page } from '@playwright/test'; + +export default class AboutPage { + readonly page: Page; + readonly mainContent: Locator; + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + } +} \ No newline at end of file diff --git a/e2e/pages/basePage.ts b/e2e/pages/basePage.ts new file mode 100644 index 000000000..59ae1afc7 --- /dev/null +++ b/e2e/pages/basePage.ts @@ -0,0 +1,42 @@ +import { test as base } from '@playwright/test'; +import AboutPage from './aboutPage'; +import HomePage from './homePage'; +import FooterComponent from './footerComponent'; +import HeaderComponent from './headerComponent'; +import CatalogPage from './catalogPage'; +import DatasetPage from './datasetPage'; +import StoryPage from './storyPage'; + +export const test = base.extend<{ + aboutPage: AboutPage; + footerComponent: FooterComponent; + headerComponent: HeaderComponent; + homePage: HomePage; + catalogPage: CatalogPage; + datasetPage: DatasetPage; + storyPage: StoryPage +}> ({ + aboutPage: async ({page}, use) => { + await use(new AboutPage(page)); + }, + catalogPage: async ({page}, use) => { + await use(new CatalogPage(page)); + }, + datasetPage: async ({page}, use) => { + await use(new DatasetPage(page)); + }, + homePage: async ({page}, use) => { + await use(new HomePage(page)); + }, + storyPage: async ({page}, use) => { + await use(new StoryPage(page)); + }, + headerComponent: async ({page}, use) => { + await use(new HeaderComponent(page)); + }, + footerComponent: async ({page}, use) => { + await use(new FooterComponent(page)); + }, +}); + +export const expect = test.expect; \ No newline at end of file diff --git a/e2e/pages/catalogPage.ts b/e2e/pages/catalogPage.ts new file mode 100644 index 000000000..64025f5df --- /dev/null +++ b/e2e/pages/catalogPage.ts @@ -0,0 +1,14 @@ +import { Locator, Page } from '@playwright/test'; + +export default class CatalogPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.header = this.mainContent.getByRole('heading', {level: 1}) + } +} \ No newline at end of file diff --git a/e2e/pages/datasetPage.ts b/e2e/pages/datasetPage.ts new file mode 100644 index 000000000..75386bca9 --- /dev/null +++ b/e2e/pages/datasetPage.ts @@ -0,0 +1,18 @@ +import { Locator, Page } from '@playwright/test'; + +export default class DatasetPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + readonly exploreDataButton: Locator; + readonly analyzeDataButton: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.header = this.mainContent.getByRole('heading', { level: 1 }) + this.exploreDataButton = this.page.getByRole('link', {name: /explore data/i} ); + this.analyzeDataButton = this.page.getByRole('button', {name: /analyze data/i} ); + } +} \ No newline at end of file diff --git a/e2e/pages/footerComponent.ts b/e2e/pages/footerComponent.ts new file mode 100644 index 000000000..90d5bdf8d --- /dev/null +++ b/e2e/pages/footerComponent.ts @@ -0,0 +1,13 @@ +import { Locator, Page } from '@playwright/test'; + +export default class FooterComponent { + readonly page: Page; + readonly footer: Locator; + readonly partners: Locator; + + constructor(page: Page) { + this.page = page; + this.footer = this.page.locator('footer'); + this.partners = this.footer.locator('div'); + } +} \ No newline at end of file diff --git a/e2e/pages/headerComponent.ts b/e2e/pages/headerComponent.ts new file mode 100644 index 000000000..96808702a --- /dev/null +++ b/e2e/pages/headerComponent.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from '@playwright/test'; + +export default class HeaderComponent { + readonly page: Page; + readonly header: Locator; + readonly welcomeLink: Locator; + readonly dataCatalogLink: Locator; + readonly analysisLink: Locator; + readonly dataInsightsLink: Locator; + readonly aboutLink: Locator; + readonly feedbackLink: Locator; + + constructor(page: Page) { + this.page = page; + this.header = this.page.getByRole('navigation'); + this.welcomeLink = this.header.getByRole('link', {name: /welcome/i}); + this.dataCatalogLink = this.header.getByRole('link', {name: / data catalog/i}); + this.analysisLink = this.header.getByRole('link', {name: /analysis/i}); + this.dataInsightsLink = this.header.getByRole('link', {name: /data insights/i}); + this.aboutLink = this.header.getByRole('link', {name: /about/i}); + this.feedbackLink = this.header.getByRole('link', {name: /feedback/i}); + } +} \ No newline at end of file diff --git a/e2e/pages/homePage.ts b/e2e/pages/homePage.ts new file mode 100644 index 000000000..5fa13895e --- /dev/null +++ b/e2e/pages/homePage.ts @@ -0,0 +1,14 @@ +import { Locator, Page } from '@playwright/test'; + +export default class HomePage { + readonly page: Page; + readonly mainContent: Locator; + readonly headingContainer: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.headingContainer = this.mainContent.locator('div').filter({ hasText: 'U.S. Greenhouse Gas CenterUniting Data and Technology to Empower Tomorrow\'s' }).nth(2) + } +} \ No newline at end of file diff --git a/e2e/pages/storyPage.ts b/e2e/pages/storyPage.ts new file mode 100644 index 000000000..358b2caa2 --- /dev/null +++ b/e2e/pages/storyPage.ts @@ -0,0 +1,14 @@ +import { Locator, Page } from '@playwright/test'; + +export default class StoryPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.header = this.mainContent.getByRole('heading', {level: 1}) + } +} \ No newline at end of file diff --git a/e2e/tests/catalog.spec.ts b/e2e/tests/catalog.spec.ts new file mode 100644 index 000000000..c9e7c4b33 --- /dev/null +++ b/e2e/tests/catalog.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from '../pages/basePage'; + +const catalogs = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['catalogs']; + +test('load catalogs on /data-catalog route', async ({ + page, + catalogPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/data-catalog'); + await expect(catalogPage.header, `catalog page should load`).toHaveText(/data catalog/i); + + for (const item of catalogs) { + const catalogCard = catalogPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).last(); + await catalogCard.scrollIntoViewIfNeeded(); + await expect(catalogCard, `${item} catalog card should load`).toBeVisible(); + }; + + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) + +}); \ No newline at end of file diff --git a/e2e/tests/catalogRouting.spec.ts b/e2e/tests/catalogRouting.spec.ts new file mode 100644 index 000000000..8161311d5 --- /dev/null +++ b/e2e/tests/catalogRouting.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from '../pages/basePage'; + +const catalogs = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['catalogs']; + +test.describe('catalog card routing', () => { + for (const item of catalogs) { + test(`${item} routes to dataset details page`, async({ + page, + catalogPage, + datasetPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/data-catalog'); + await expect(catalogPage.header, `catalog page should load`).toHaveText(/data catalog/i); + + const catalogCard = catalogPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).first(); + await catalogCard.scrollIntoViewIfNeeded(); + await catalogCard.click({force: true}); + + await expect(datasetPage.header.filter({ hasText: item}), `${item} page should load`).toBeVisible(); + + // 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 diff --git a/e2e/tests/stories.spec.ts b/e2e/tests/stories.spec.ts new file mode 100644 index 000000000..4b3adb0fe --- /dev/null +++ b/e2e/tests/stories.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '../pages/basePage'; + +const stories = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['stories']; + +test('load stories on /stories route', async ({ + page, + storyPage, + }) => { + + + + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/stories'); + await expect(storyPage.header, `data stories page should load`).toHaveText(/data stories/i); + + for (const item of stories) { + const storiesCard = storyPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).first(); + await storiesCard.scrollIntoViewIfNeeded(); + await expect(storiesCard, `${item} story card should load`).toBeVisible(); + }; + + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) + +}); \ No newline at end of file diff --git a/e2e/tests/storiesRouting.spec.ts b/e2e/tests/storiesRouting.spec.ts new file mode 100644 index 000000000..8336f16cf --- /dev/null +++ b/e2e/tests/storiesRouting.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '../pages/basePage'; + +const stories = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['stories']; + +test.describe('stories card routing', () => { + for (const item of stories) { + test(`${item} routes to dataset details page`, async({ + page, + storyPage, + datasetPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/stories'); + await expect(storyPage.header, `stories page should load`).toHaveText(/data stories/i); + + const storyCard = storyPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).last(); + await storyCard.scrollIntoViewIfNeeded(); + await storyCard.click({force: true}); + await expect(datasetPage.header.filter({ hasText: item}), `${item} page should load`).toBeVisible(); + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) + }) + } + +}); \ No newline at end of file diff --git a/package.json b/package.json index 891805b15..83e01e6ba 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "stage": "NODE_ENV=staging .veda/veda", "clean": ".veda/veda clean", "local-cms": "npx netlify-cms-proxy-server", - "test": "NODE_ENV=test .veda/veda test" + "test": "NODE_ENV=test .veda/veda test", + "pretest:e2e": "node e2e/generateTestData.js", + "test:e2e": "yarn playwright test" }, "browserslist": "> 0.5%, last 2 versions, not dead", "engines": { @@ -20,7 +22,11 @@ "devDependencies": { "@parcel/packager-raw-url": "2.7.0", "@parcel/transformer-webmanifest": "2.7.0", + "@playwright/test": "^1.41.1", + "@types/node": "^20.11.6", "dotenv": "^10.0.0", + "fast-glob": "^3.3.2", + "gray-matter": "^4.0.3", "netlify-cms-proxy-server": "^1.3.24" }, "alias": { diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..08dd56875 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,58 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + // Global single test timeout + timeout: 300000, + // For expect calls + expect: { + timeout: 180000, + }, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : 3, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:9000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'retain-on-failure', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + // uncomment to also run tests in Firefox and webkit + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn serve', + url: 'http://localhost:9000', + reuseExistingServer: !process.env.CI, + }, +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c998a22d2..6ec4d6e7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -149,6 +149,27 @@ resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407" integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@parcel/cache@2.7.0": version "2.7.0" resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.7.0.tgz#cc4b99685c7ff0fc20fbc321f4b6850d6e0c6811" @@ -313,6 +334,20 @@ chrome-trace-event "^1.0.2" nullthrows "^1.1.1" +"@playwright/test@^1.41.1": + version "1.41.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.41.1.tgz#6954139ed4a67999f1b17460aa3d184f4b334f18" + integrity sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw== + dependencies: + playwright "1.41.1" + +"@types/node@^20.11.6": + version "20.11.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.6.tgz#6adf4241460e28be53836529c033a41985f85b6e" + integrity sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q== + dependencies: + undici-types "~5.26.4" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -328,6 +363,13 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -535,6 +577,11 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -577,6 +624,31 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +fast-glob@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fastq@^1.6.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.0.tgz#ca5e1a90b5e68f97fc8b61330d5819b82f5fab03" + integrity sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w== + dependencies: + reusify "^1.0.4" + fecha@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" @@ -617,6 +689,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -631,6 +708,23 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.3" +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +gray-matter@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.3.tgz#e893c064825de73ea1f5f7d88c7a9f7274288798" + integrity sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q== + dependencies: + js-yaml "^3.13.1" + kind-of "^6.0.2" + section-matter "^1.0.0" + strip-bom-string "^1.0.0" + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -681,12 +775,17 @@ is-arrayish@^0.3.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== +is-extendable@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-glob@^4.0.3: +is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -703,11 +802,24 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + json5@^2.2.1: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + kuler@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" @@ -752,12 +864,17 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.5: +micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -935,6 +1052,20 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +playwright-core@1.41.1: + version "1.41.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.41.1.tgz#9c152670010d9d6f970f34b68e3e935d3c487431" + integrity sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg== + +playwright@1.41.1: + version "1.41.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.41.1.tgz#83325f34165840d019355c2a78a50f21ed9b9c85" + integrity sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ== + dependencies: + playwright-core "1.41.1" + optionalDependencies: + fsevents "2.3.2" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -950,6 +1081,11 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -974,6 +1110,18 @@ readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -994,6 +1142,14 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +section-matter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" + integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA== + dependencies: + extend-shallow "^2.0.1" + kind-of "^6.0.0" + semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -1058,6 +1214,11 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + stack-trace@0.0.x: version "0.0.10" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" @@ -1075,6 +1236,11 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +strip-bom-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" + integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -1117,6 +1283,11 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" From 62151da4a25d79d0a88a107bf7d1eb9c3a43278b Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Wed, 7 Feb 2024 09:59:12 -0700 Subject: [PATCH 2/9] add analysis flow test --- e2e/pages/analysisPage.ts | 55 ++++++++++++++++++++++++++++++++ e2e/pages/analysisResultsPage.ts | 13 ++++++++ e2e/pages/basePage.ts | 10 ++++++ e2e/tests/analysis.spec.ts | 44 +++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 e2e/pages/analysisPage.ts create mode 100644 e2e/pages/analysisResultsPage.ts create mode 100644 e2e/tests/analysis.spec.ts diff --git a/e2e/pages/analysisPage.ts b/e2e/pages/analysisPage.ts new file mode 100644 index 000000000..36f267a1e --- /dev/null +++ b/e2e/pages/analysisPage.ts @@ -0,0 +1,55 @@ +import { Locator, Page, test } from '@playwright/test'; + +export default class AnalysisPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + readonly mapboxCanvas: Locator; + readonly generateAnalysisButton: Locator; + readonly datasetOptions: Locator; + readonly datasetCheckbox: Locator; + + + constructor(page: Page) { + this.page = page; + 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'); + } + + async drawPolygon (polygonCorners: number[][]) { + await test.step('draw polygon on mapbox canvas box', async () => { + if(polygonCorners.length < 3) { + throw new Error('polygon in drawPolygon must have >=3 corners') + } + // mutating corners array to have all but the final corner + const finalCorner = polygonCorners.pop()|| []; + + // single click each remaining corner + for (const corner of polygonCorners) { + await this.mapboxCanvas.click({ + position: { + x: corner[0], + y: corner[1] + } + }); + } + // double click on final corner + await this.mapboxCanvas.dblclick({ + position: { + x: finalCorner[0], + y: finalCorner[1] + } + }); + }) + } + + async clickDatasetOption (index: number) { + test.step(`clicking dataset number ${index}`, async () => { + this.datasetCheckbox.nth(index).locator('..').click(); + }) + } +} \ No newline at end of file diff --git a/e2e/pages/analysisResultsPage.ts b/e2e/pages/analysisResultsPage.ts new file mode 100644 index 000000000..b018735d7 --- /dev/null +++ b/e2e/pages/analysisResultsPage.ts @@ -0,0 +1,13 @@ +import { Locator, Page } from '@playwright/test'; + +export default class AnalysisResultsPage { + readonly page: Page; + readonly analysisCards: Locator; + + + constructor(page: Page) { + this.page = page; + this.analysisCards = this.page.getByTestId('analysisCards'); + } + +} \ No newline at end of file diff --git a/e2e/pages/basePage.ts b/e2e/pages/basePage.ts index 59ae1afc7..b2dde30a1 100644 --- a/e2e/pages/basePage.ts +++ b/e2e/pages/basePage.ts @@ -1,5 +1,7 @@ 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'; @@ -9,6 +11,8 @@ import StoryPage from './storyPage'; export const test = base.extend<{ aboutPage: AboutPage; + analysisPage: AnalysisPage; + analysisResultsPage: AnalysisResultsPage; footerComponent: FooterComponent; headerComponent: HeaderComponent; homePage: HomePage; @@ -19,6 +23,12 @@ export const test = base.extend<{ aboutPage: async ({page}, use) => { await use(new AboutPage(page)); }, + analysisPage: async ({page}, use) => { + await use(new AnalysisPage(page)); + }, + analysisResultsPage: async ({page}, use) => { + await use(new AnalysisResultsPage(page)); + }, catalogPage: async ({page}, use) => { await use(new CatalogPage(page)); }, diff --git a/e2e/tests/analysis.spec.ts b/e2e/tests/analysis.spec.ts new file mode 100644 index 000000000..988c646fe --- /dev/null +++ b/e2e/tests/analysis.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from '../pages/basePage'; + +test('load /analysis route', async ({ + page, + analysisPage, + analysisResultsPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${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(); + + 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]; + + await analysisPage.mapboxCanvas.click(); + + await analysisPage.drawPolygon([firstCorner, secondCorner, thirdCorner]) + + await analysisPage.clickDatasetOption(1); + + const searchResponsePromise = page.waitForResponse(/\/search/i); + await analysisPage.generateAnalysisButton.click({force: true }); + + + 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(); + +}); \ No newline at end of file From 258e7fcba7004d6852a4763b5c6eef34f8a4ff4e Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Fri, 16 Feb 2024 17:20:47 -0700 Subject: [PATCH 3/9] move tests into ui submodule --- e2e/README.md | 19 +- e2e/generateTestData.js | 10 +- e2e/pages/aboutPage.ts | 11 -- e2e/pages/analysisPage.ts | 55 ------ e2e/pages/analysisResultsPage.ts | 13 -- e2e/pages/basePage.ts | 52 ----- e2e/pages/catalogPage.ts | 14 -- e2e/pages/datasetPage.ts | 18 -- e2e/pages/footerComponent.ts | 13 -- e2e/pages/headerComponent.ts | 23 --- e2e/pages/homePage.ts | 14 -- e2e/pages/storyPage.ts | 14 -- e2e/tests/analysis.spec.ts | 44 ----- e2e/tests/catalog.spec.ts | 27 --- e2e/tests/catalogRouting.spec.ts | 35 ---- e2e/tests/stories.spec.ts | 30 --- e2e/tests/storiesRouting.spec.ts | 30 --- package.json | 7 +- playwright.config.ts | 58 ------ yarn.lock | 326 ++++++++++++++++++++++++++++--- 20 files changed, 313 insertions(+), 500 deletions(-) delete mode 100644 e2e/pages/aboutPage.ts delete mode 100644 e2e/pages/analysisPage.ts delete mode 100644 e2e/pages/analysisResultsPage.ts delete mode 100644 e2e/pages/basePage.ts delete mode 100644 e2e/pages/catalogPage.ts delete mode 100644 e2e/pages/datasetPage.ts delete mode 100644 e2e/pages/footerComponent.ts delete mode 100644 e2e/pages/headerComponent.ts delete mode 100644 e2e/pages/homePage.ts delete mode 100644 e2e/pages/storyPage.ts delete mode 100644 e2e/tests/analysis.spec.ts delete mode 100644 e2e/tests/catalog.spec.ts delete mode 100644 e2e/tests/catalogRouting.spec.ts delete mode 100644 e2e/tests/stories.spec.ts delete mode 100644 e2e/tests/storiesRouting.spec.ts delete mode 100644 playwright.config.ts diff --git a/e2e/README.md b/e2e/README.md index 075171b81..85ca19755 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,6 +1,6 @@ # Playwright E2E Testing -This testing suite is for End to End testing the VEDA website via the UI using Playwright. It works by serving a local version of the site using yarn serve and performing UI checks against that locally hosted site. The suite is designed to generate a `playwrightTestData.json` that contains the list of all Catalog and Story names. This is done by parsing the `name` field in the `*.mdx` files of the `/datasets` and `/stories` directories. +The veda-config can be tested end-to-end to by leveraging a suite of Playwright tests located in the veda-ui git submodule. It works by serving a local version of the site using yarn serve and performing UI checks against that locally hosted site. The suite is designed to generate a `playwrightTestData.json` that contains the list of all Catalog and Story names. This is done by parsing the `name` field in the `*.mdx` files of the `/datasets` and `/stories` directories of the config repo. ## Running the test suite @@ -11,25 +11,12 @@ The test suite can be run via the `yarn test:e2e` script. There is a `prtest:e2e The end to end tests are organized in the `/e2e` directory. The tests have been written following a [Page Object Model](https://martinfowler.com/bliki/PageObject.html) pattern. Supporting files within the repo are in the following structure: -```text -/e2e - │ - │─── README.md - │─── playwright.config.ts - imports our global setup, defines preferred browsers, & number of retries - │─── generateTestData.js - parses mdx files and creates a json based on their metadata - └─── /pages - │ └─── basePage.ts - imports all seeded data and PageObjects into our `test` object. - │ │ - │ └─── [PAGENAME]Page.ts - The page objects. UI elements interacted with on a page are defined once to keep tests DRY and minimize test changes if layout changes. - └─── tests - our actual tests -``` - ## Updating Tests -If the layout of a page changes, then the tests may no longer be able to interact with locators. These locators are defined in the Page Objects defined in `/e2e/pages`. The Playwright framework provides multiple ways to choose elements to interact with. The recomended ones are defined in the [Playwright documentation](https://playwright.dev/docs/locators#quick-guide). +If the layout of a page changes, then the tests may no longer be able to interact with locators. These locators are defined in the Page Objects defined in `/e2e/pages`. The Playwright framework provides multiple ways to choose elements to interact with. The recommended ones are defined in the [Playwright documentation](https://playwright.dev/docs/locators#quick-guide). Any new pages will need to have new page objects created and then imported into the `basePage.ts` file following th format of existing pages. This allows all tests to reference the page. ## Output -Playwright will generate an html report with test results. This report will show all tests that were run, and will allow a user to view the results of any failed tests. +Playwright will generate an html report with test results. This report will show all tests that were run, and will allow a user to view the results of any failed tests. This report can be viewed with the script `yarn report` from the config repo. diff --git a/e2e/generateTestData.js b/e2e/generateTestData.js index 89c83ca9a..1b0477117 100644 --- a/e2e/generateTestData.js +++ b/e2e/generateTestData.js @@ -6,11 +6,14 @@ 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) { @@ -19,11 +22,12 @@ for (const story of storyPaths) { } const testDataJson = { - "catalogs": catalogNames, - "stories": storyNames + catalogs: catalogNames, + datasetIds: datasetIds, + stories: storyNames } -fs.writeFile(path.join(__dirname, 'playwrightTestData.json'), JSON.stringify(testDataJson), err => { +fs.writeFile(path.join(__dirname, '../.veda/ui/e2e/playwrightTestData.json'), JSON.stringify(testDataJson), err => { if (err) { console.error(err); } else { diff --git a/e2e/pages/aboutPage.ts b/e2e/pages/aboutPage.ts deleted file mode 100644 index a9e84eac3..000000000 --- a/e2e/pages/aboutPage.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class AboutPage { - readonly page: Page; - readonly mainContent: Locator; - - constructor(page: Page) { - this.page = page; - this.mainContent = this.page.getByRole('main'); - } -} \ No newline at end of file diff --git a/e2e/pages/analysisPage.ts b/e2e/pages/analysisPage.ts deleted file mode 100644 index 36f267a1e..000000000 --- a/e2e/pages/analysisPage.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Locator, Page, test } from '@playwright/test'; - -export default class AnalysisPage { - readonly page: Page; - readonly mainContent: Locator; - readonly header: Locator; - readonly mapboxCanvas: Locator; - readonly generateAnalysisButton: Locator; - readonly datasetOptions: Locator; - readonly datasetCheckbox: Locator; - - - constructor(page: Page) { - this.page = page; - 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'); - } - - async drawPolygon (polygonCorners: number[][]) { - await test.step('draw polygon on mapbox canvas box', async () => { - if(polygonCorners.length < 3) { - throw new Error('polygon in drawPolygon must have >=3 corners') - } - // mutating corners array to have all but the final corner - const finalCorner = polygonCorners.pop()|| []; - - // single click each remaining corner - for (const corner of polygonCorners) { - await this.mapboxCanvas.click({ - position: { - x: corner[0], - y: corner[1] - } - }); - } - // double click on final corner - await this.mapboxCanvas.dblclick({ - position: { - x: finalCorner[0], - y: finalCorner[1] - } - }); - }) - } - - async clickDatasetOption (index: number) { - test.step(`clicking dataset number ${index}`, async () => { - this.datasetCheckbox.nth(index).locator('..').click(); - }) - } -} \ No newline at end of file diff --git a/e2e/pages/analysisResultsPage.ts b/e2e/pages/analysisResultsPage.ts deleted file mode 100644 index b018735d7..000000000 --- a/e2e/pages/analysisResultsPage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class AnalysisResultsPage { - readonly page: Page; - readonly analysisCards: Locator; - - - constructor(page: Page) { - this.page = page; - this.analysisCards = this.page.getByTestId('analysisCards'); - } - -} \ No newline at end of file diff --git a/e2e/pages/basePage.ts b/e2e/pages/basePage.ts deleted file mode 100644 index b2dde30a1..000000000 --- a/e2e/pages/basePage.ts +++ /dev/null @@ -1,52 +0,0 @@ -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 StoryPage from './storyPage'; - -export const test = base.extend<{ - aboutPage: AboutPage; - analysisPage: AnalysisPage; - analysisResultsPage: AnalysisResultsPage; - footerComponent: FooterComponent; - headerComponent: HeaderComponent; - homePage: HomePage; - catalogPage: CatalogPage; - datasetPage: DatasetPage; - storyPage: StoryPage -}> ({ - aboutPage: async ({page}, use) => { - await use(new AboutPage(page)); - }, - analysisPage: async ({page}, use) => { - await use(new AnalysisPage(page)); - }, - analysisResultsPage: async ({page}, use) => { - await use(new AnalysisResultsPage(page)); - }, - catalogPage: async ({page}, use) => { - await use(new CatalogPage(page)); - }, - datasetPage: async ({page}, use) => { - await use(new DatasetPage(page)); - }, - homePage: async ({page}, use) => { - await use(new HomePage(page)); - }, - storyPage: async ({page}, use) => { - await use(new StoryPage(page)); - }, - headerComponent: async ({page}, use) => { - await use(new HeaderComponent(page)); - }, - footerComponent: async ({page}, use) => { - await use(new FooterComponent(page)); - }, -}); - -export const expect = test.expect; \ No newline at end of file diff --git a/e2e/pages/catalogPage.ts b/e2e/pages/catalogPage.ts deleted file mode 100644 index 64025f5df..000000000 --- a/e2e/pages/catalogPage.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class CatalogPage { - readonly page: Page; - readonly mainContent: Locator; - readonly header: Locator; - - - constructor(page: Page) { - this.page = page; - this.mainContent = this.page.getByRole('main'); - this.header = this.mainContent.getByRole('heading', {level: 1}) - } -} \ No newline at end of file diff --git a/e2e/pages/datasetPage.ts b/e2e/pages/datasetPage.ts deleted file mode 100644 index 75386bca9..000000000 --- a/e2e/pages/datasetPage.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class DatasetPage { - readonly page: Page; - readonly mainContent: Locator; - readonly header: Locator; - readonly exploreDataButton: Locator; - readonly analyzeDataButton: Locator; - - - constructor(page: Page) { - this.page = page; - this.mainContent = this.page.getByRole('main'); - this.header = this.mainContent.getByRole('heading', { level: 1 }) - this.exploreDataButton = this.page.getByRole('link', {name: /explore data/i} ); - this.analyzeDataButton = this.page.getByRole('button', {name: /analyze data/i} ); - } -} \ No newline at end of file diff --git a/e2e/pages/footerComponent.ts b/e2e/pages/footerComponent.ts deleted file mode 100644 index 90d5bdf8d..000000000 --- a/e2e/pages/footerComponent.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class FooterComponent { - readonly page: Page; - readonly footer: Locator; - readonly partners: Locator; - - constructor(page: Page) { - this.page = page; - this.footer = this.page.locator('footer'); - this.partners = this.footer.locator('div'); - } -} \ No newline at end of file diff --git a/e2e/pages/headerComponent.ts b/e2e/pages/headerComponent.ts deleted file mode 100644 index 96808702a..000000000 --- a/e2e/pages/headerComponent.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class HeaderComponent { - readonly page: Page; - readonly header: Locator; - readonly welcomeLink: Locator; - readonly dataCatalogLink: Locator; - readonly analysisLink: Locator; - readonly dataInsightsLink: Locator; - readonly aboutLink: Locator; - readonly feedbackLink: Locator; - - constructor(page: Page) { - this.page = page; - this.header = this.page.getByRole('navigation'); - this.welcomeLink = this.header.getByRole('link', {name: /welcome/i}); - this.dataCatalogLink = this.header.getByRole('link', {name: / data catalog/i}); - this.analysisLink = this.header.getByRole('link', {name: /analysis/i}); - this.dataInsightsLink = this.header.getByRole('link', {name: /data insights/i}); - this.aboutLink = this.header.getByRole('link', {name: /about/i}); - this.feedbackLink = this.header.getByRole('link', {name: /feedback/i}); - } -} \ No newline at end of file diff --git a/e2e/pages/homePage.ts b/e2e/pages/homePage.ts deleted file mode 100644 index 5fa13895e..000000000 --- a/e2e/pages/homePage.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class HomePage { - readonly page: Page; - readonly mainContent: Locator; - readonly headingContainer: Locator; - - - constructor(page: Page) { - this.page = page; - this.mainContent = this.page.getByRole('main'); - this.headingContainer = this.mainContent.locator('div').filter({ hasText: 'U.S. Greenhouse Gas CenterUniting Data and Technology to Empower Tomorrow\'s' }).nth(2) - } -} \ No newline at end of file diff --git a/e2e/pages/storyPage.ts b/e2e/pages/storyPage.ts deleted file mode 100644 index 358b2caa2..000000000 --- a/e2e/pages/storyPage.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class StoryPage { - readonly page: Page; - readonly mainContent: Locator; - readonly header: Locator; - - - constructor(page: Page) { - this.page = page; - this.mainContent = this.page.getByRole('main'); - this.header = this.mainContent.getByRole('heading', {level: 1}) - } -} \ No newline at end of file diff --git a/e2e/tests/analysis.spec.ts b/e2e/tests/analysis.spec.ts deleted file mode 100644 index 988c646fe..000000000 --- a/e2e/tests/analysis.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { test, expect } from '../pages/basePage'; - -test('load /analysis route', async ({ - page, - analysisPage, - analysisResultsPage, - }) => { - let pageErrorCalled = false; - // Log all uncaught errors to the terminal - page.on('pageerror', exception => { - console.log(`Uncaught exception: "${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(); - - 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]; - - await analysisPage.mapboxCanvas.click(); - - await analysisPage.drawPolygon([firstCorner, secondCorner, thirdCorner]) - - await analysisPage.clickDatasetOption(1); - - const searchResponsePromise = page.waitForResponse(/\/search/i); - await analysisPage.generateAnalysisButton.click({force: true }); - - - 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(); - -}); \ No newline at end of file diff --git a/e2e/tests/catalog.spec.ts b/e2e/tests/catalog.spec.ts deleted file mode 100644 index c9e7c4b33..000000000 --- a/e2e/tests/catalog.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { test, expect } from '../pages/basePage'; - -const catalogs = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['catalogs']; - -test('load catalogs on /data-catalog route', async ({ - page, - catalogPage, - }) => { - let pageErrorCalled = false; - // Log all uncaught errors to the terminal - page.on('pageerror', exception => { - console.log(`Uncaught exception: "${exception}"`); - pageErrorCalled = true; - }); - - await page.goto('/data-catalog'); - await expect(catalogPage.header, `catalog page should load`).toHaveText(/data catalog/i); - - for (const item of catalogs) { - const catalogCard = catalogPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).last(); - await catalogCard.scrollIntoViewIfNeeded(); - await expect(catalogCard, `${item} catalog card should load`).toBeVisible(); - }; - - expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) - -}); \ No newline at end of file diff --git a/e2e/tests/catalogRouting.spec.ts b/e2e/tests/catalogRouting.spec.ts deleted file mode 100644 index 8161311d5..000000000 --- a/e2e/tests/catalogRouting.spec.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { test, expect } from '../pages/basePage'; - -const catalogs = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['catalogs']; - -test.describe('catalog card routing', () => { - for (const item of catalogs) { - test(`${item} routes to dataset details page`, async({ - page, - catalogPage, - datasetPage, - }) => { - let pageErrorCalled = false; - // Log all uncaught errors to the terminal - page.on('pageerror', exception => { - console.log(`Uncaught exception: "${exception}"`); - pageErrorCalled = true; - }); - - await page.goto('/data-catalog'); - await expect(catalogPage.header, `catalog page should load`).toHaveText(/data catalog/i); - - const catalogCard = catalogPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).first(); - await catalogCard.scrollIntoViewIfNeeded(); - await catalogCard.click({force: true}); - - await expect(datasetPage.header.filter({ hasText: item}), `${item} page should load`).toBeVisible(); - - // 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 diff --git a/e2e/tests/stories.spec.ts b/e2e/tests/stories.spec.ts deleted file mode 100644 index 4b3adb0fe..000000000 --- a/e2e/tests/stories.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { test, expect } from '../pages/basePage'; - -const stories = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['stories']; - -test('load stories on /stories route', async ({ - page, - storyPage, - }) => { - - - - let pageErrorCalled = false; - // Log all uncaught errors to the terminal - page.on('pageerror', exception => { - console.log(`Uncaught exception: "${exception}"`); - pageErrorCalled = true; - }); - - await page.goto('/stories'); - await expect(storyPage.header, `data stories page should load`).toHaveText(/data stories/i); - - for (const item of stories) { - const storiesCard = storyPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).first(); - await storiesCard.scrollIntoViewIfNeeded(); - await expect(storiesCard, `${item} story card should load`).toBeVisible(); - }; - - expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) - -}); \ No newline at end of file diff --git a/e2e/tests/storiesRouting.spec.ts b/e2e/tests/storiesRouting.spec.ts deleted file mode 100644 index 8336f16cf..000000000 --- a/e2e/tests/storiesRouting.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { test, expect } from '../pages/basePage'; - -const stories = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['stories']; - -test.describe('stories card routing', () => { - for (const item of stories) { - test(`${item} routes to dataset details page`, async({ - page, - storyPage, - datasetPage, - }) => { - let pageErrorCalled = false; - // Log all uncaught errors to the terminal - page.on('pageerror', exception => { - console.log(`Uncaught exception: "${exception}"`); - pageErrorCalled = true; - }); - - await page.goto('/stories'); - await expect(storyPage.header, `stories page should load`).toHaveText(/data stories/i); - - const storyCard = storyPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).last(); - await storyCard.scrollIntoViewIfNeeded(); - await storyCard.click({force: true}); - await expect(datasetPage.header.filter({ hasText: item}), `${item} page should load`).toBeVisible(); - expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) - }) - } - -}); \ No newline at end of file diff --git a/package.json b/package.json index 83e01e6ba..15291e0ae 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "local-cms": "npx netlify-cms-proxy-server", "test": "NODE_ENV=test .veda/veda test", "pretest:e2e": "node e2e/generateTestData.js", - "test:e2e": "yarn playwright test" + "test:e2e": "start-server-and-test 'yarn serve' http://127.0.0.1:9000/ 'cd .veda/ui && yarn playwright test'", + "report": "cd .veda/ui && yarn playwright show-report" }, "browserslist": "> 0.5%, last 2 versions, not dead", "engines": { @@ -22,12 +23,12 @@ "devDependencies": { "@parcel/packager-raw-url": "2.7.0", "@parcel/transformer-webmanifest": "2.7.0", - "@playwright/test": "^1.41.1", "@types/node": "^20.11.6", "dotenv": "^10.0.0", "fast-glob": "^3.3.2", "gray-matter": "^4.0.3", - "netlify-cms-proxy-server": "^1.3.24" + "netlify-cms-proxy-server": "^1.3.24", + "start-server-and-test": "^2.0.3" }, "alias": { "react": "./.veda/ui/node_modules/react", diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index 08dd56875..000000000 --- a/playwright.config.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - testDir: './e2e', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - // Global single test timeout - timeout: 300000, - // For expect calls - expect: { - timeout: 180000, - }, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : 3, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:9000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'retain-on-failure', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - // uncomment to also run tests in Firefox and webkit - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - ], - - /* Run your local dev server before starting the tests */ - webServer: { - command: 'yarn serve', - url: 'http://localhost:9000', - reuseExistingServer: !process.env.CI, - }, -}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 6ec4d6e7a..44b5fb96c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,7 +28,7 @@ resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== -"@hapi/hoek@^9.0.0": +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== @@ -49,7 +49,7 @@ resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== -"@hapi/topo@^5.0.0": +"@hapi/topo@^5.0.0", "@hapi/topo@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== @@ -334,12 +334,22 @@ chrome-trace-event "^1.0.2" nullthrows "^1.1.1" -"@playwright/test@^1.41.1": - version "1.41.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.41.1.tgz#6954139ed4a67999f1b17460aa3d184f4b334f18" - integrity sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw== +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== dependencies: - playwright "1.41.1" + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== "@types/node@^20.11.6": version "20.11.6" @@ -363,6 +373,11 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -387,6 +402,20 @@ async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.6.1: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" @@ -394,6 +423,11 @@ basic-auth@~2.0.1: dependencies: safe-buffer "5.1.2" +bluebird@3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -440,6 +474,11 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-more-types@2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -493,6 +532,13 @@ colorspace@1.1.x: color "^3.1.3" text-hex "1.0.x" +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -523,6 +569,15 @@ cors@^2.8.5: object-assign "^4" vary "^1" +cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -530,13 +585,18 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4.1.1, debug@^4.3.4: +debug@4.3.4, debug@^4.1.1, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -557,6 +617,11 @@ dotenv@^10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +duplexer@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -587,6 +652,34 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-stream@=3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + integrity sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g== + dependencies: + duplexer "~0.1.1" + from "~0" + map-stream "~0.1.0" + pause-stream "0.0.11" + split "0.3" + stream-combiner "~0.0.4" + through "~2.3.1" + +execa@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + express@^4.17.1: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -679,6 +772,20 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== +follow-redirects@^1.15.4: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -689,10 +796,10 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -fsevents@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== +from@~0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" + integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g== function-bind@^1.1.1: version "1.1.1" @@ -708,6 +815,11 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.3" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -753,6 +865,11 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -802,6 +919,22 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +joi@^17.11.0: + version "17.12.1" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.1.tgz#3347ecf4cd3301962d42191c021b165eef1f395b" + integrity sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -825,6 +958,11 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== +lazy-ass@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== + lmdb@2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.5.2.tgz#37e28a9fb43405f4dc48c44cec0e13a14c4a6ff1" @@ -843,6 +981,11 @@ lmdb@2.5.2: "@lmdb/lmdb-linux-x64" "2.5.2" "@lmdb/lmdb-win32-x64" "2.5.2" +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + logform@^2.3.2, logform@^2.4.0: version "2.4.2" resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.2.tgz#a617983ac0334d0c3b942c34945380062795b47c" @@ -854,6 +997,11 @@ logform@^2.3.2, logform@^2.4.0: safe-stable-stringify "^2.3.1" triple-beam "^1.3.0" +map-stream@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" + integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -864,6 +1012,11 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -887,7 +1040,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -899,6 +1052,16 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimist@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + morgan@^1.9.1: version "1.10.0" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" @@ -991,6 +1154,13 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -1032,6 +1202,13 @@ one-time@^1.0.0: dependencies: fn.name "1.x.x" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + ordered-binary@^1.2.4: version "1.4.0" resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.4.0.tgz#6bb53d44925f3b8afc33d1eed0fa15693b211389" @@ -1042,30 +1219,28 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +pause-stream@0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A== + dependencies: + through "~2.3" + picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -playwright-core@1.41.1: - version "1.41.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.41.1.tgz#9c152670010d9d6f970f34b68e3e935d3c487431" - integrity sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg== - -playwright@1.41.1: - version "1.41.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.41.1.tgz#83325f34165840d019355c2a78a50f21ed9b9c85" - integrity sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ== - dependencies: - playwright-core "1.41.1" - optionalDependencies: - fsevents "2.3.2" - proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -1074,6 +1249,18 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +ps-tree@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd" + integrity sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA== + dependencies: + event-stream "=3.3.4" + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -1122,6 +1309,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -1189,6 +1383,18 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -1198,6 +1404,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + simple-git@^3.0.0: version "3.15.1" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.15.1.tgz#57f595682cb0c2475d5056da078a05c8715a25ef" @@ -1214,6 +1425,13 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +split@0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + integrity sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA== + dependencies: + through "2" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -1224,11 +1442,32 @@ stack-trace@0.0.x: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== +start-server-and-test@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-2.0.3.tgz#15c53c85e23cba7698b498b8a2598cab95f3f802" + integrity sha512-QsVObjfjFZKJE6CS6bSKNwWZCKBG6975/jKRPPGFfFh+yOQglSeGXiNWjzgQNXdphcBI9nXbyso9tPfX4YAUhg== + dependencies: + arg "^5.0.2" + bluebird "3.7.2" + check-more-types "2.24.0" + debug "4.3.4" + execa "5.1.1" + lazy-ass "1.6.0" + ps-tree "1.2.0" + wait-on "7.2.0" + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +stream-combiner@~0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" + integrity sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw== + dependencies: + duplexer "~0.1.1" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1241,6 +1480,11 @@ strip-bom-string@^1.0.0: resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -1253,6 +1497,11 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== +through@2, through@~2.3, through@~2.3.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -1270,6 +1519,11 @@ triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tslib@^2.3.1: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" @@ -1313,6 +1567,17 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +wait-on@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.2.0.tgz#d76b20ed3fc1e2bebc051fae5c1ff93be7892928" + integrity sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ== + dependencies: + axios "^1.6.1" + joi "^17.11.0" + lodash "^4.17.21" + minimist "^1.2.8" + rxjs "^7.8.1" + weak-lru-cache@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz#fdbb6741f36bae9540d12f480ce8254060dccd19" @@ -1323,6 +1588,13 @@ what-the-diff@^0.6.0: resolved "https://registry.yarnpkg.com/what-the-diff/-/what-the-diff-0.6.0.tgz#445cc56a9d8ee9aea0ee1ed943f4957ae009291e" integrity sha512-8BgQ4uo4cxojRXvCIcqDpH4QHaq0Ksn2P3LYfztylC5LDSwZKuGHf0Wf7sAStjPLTcB8eCB8pJJcPQSWfhZlkg== +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + winston-transport@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" From 2f6b1a6036d0078329fdb4f3a43a55d8052bc011 Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Thu, 29 Feb 2024 13:08:32 -0700 Subject: [PATCH 4/9] Revert "move tests into ui submodule" This reverts commit 258e7fcba7004d6852a4763b5c6eef34f8a4ff4e. --- e2e/README.md | 19 +- e2e/generateTestData.js | 10 +- e2e/pages/aboutPage.ts | 11 ++ e2e/pages/analysisPage.ts | 55 ++++++ e2e/pages/analysisResultsPage.ts | 13 ++ e2e/pages/basePage.ts | 52 +++++ e2e/pages/catalogPage.ts | 14 ++ e2e/pages/datasetPage.ts | 18 ++ e2e/pages/footerComponent.ts | 13 ++ e2e/pages/headerComponent.ts | 23 +++ e2e/pages/homePage.ts | 14 ++ e2e/pages/storyPage.ts | 14 ++ e2e/tests/analysis.spec.ts | 44 +++++ e2e/tests/catalog.spec.ts | 27 +++ e2e/tests/catalogRouting.spec.ts | 35 ++++ e2e/tests/stories.spec.ts | 30 +++ e2e/tests/storiesRouting.spec.ts | 30 +++ package.json | 7 +- playwright.config.ts | 58 ++++++ yarn.lock | 326 +++---------------------------- 20 files changed, 500 insertions(+), 313 deletions(-) create mode 100644 e2e/pages/aboutPage.ts create mode 100644 e2e/pages/analysisPage.ts create mode 100644 e2e/pages/analysisResultsPage.ts create mode 100644 e2e/pages/basePage.ts create mode 100644 e2e/pages/catalogPage.ts create mode 100644 e2e/pages/datasetPage.ts create mode 100644 e2e/pages/footerComponent.ts create mode 100644 e2e/pages/headerComponent.ts create mode 100644 e2e/pages/homePage.ts create mode 100644 e2e/pages/storyPage.ts create mode 100644 e2e/tests/analysis.spec.ts create mode 100644 e2e/tests/catalog.spec.ts create mode 100644 e2e/tests/catalogRouting.spec.ts create mode 100644 e2e/tests/stories.spec.ts create mode 100644 e2e/tests/storiesRouting.spec.ts create mode 100644 playwright.config.ts diff --git a/e2e/README.md b/e2e/README.md index 85ca19755..075171b81 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,6 +1,6 @@ # Playwright E2E Testing -The veda-config can be tested end-to-end to by leveraging a suite of Playwright tests located in the veda-ui git submodule. It works by serving a local version of the site using yarn serve and performing UI checks against that locally hosted site. The suite is designed to generate a `playwrightTestData.json` that contains the list of all Catalog and Story names. This is done by parsing the `name` field in the `*.mdx` files of the `/datasets` and `/stories` directories of the config repo. +This testing suite is for End to End testing the VEDA website via the UI using Playwright. It works by serving a local version of the site using yarn serve and performing UI checks against that locally hosted site. The suite is designed to generate a `playwrightTestData.json` that contains the list of all Catalog and Story names. This is done by parsing the `name` field in the `*.mdx` files of the `/datasets` and `/stories` directories. ## Running the test suite @@ -11,12 +11,25 @@ The test suite can be run via the `yarn test:e2e` script. There is a `prtest:e2e The end to end tests are organized in the `/e2e` directory. The tests have been written following a [Page Object Model](https://martinfowler.com/bliki/PageObject.html) pattern. Supporting files within the repo are in the following structure: +```text +/e2e + │ + │─── README.md + │─── playwright.config.ts - imports our global setup, defines preferred browsers, & number of retries + │─── generateTestData.js - parses mdx files and creates a json based on their metadata + └─── /pages + │ └─── basePage.ts - imports all seeded data and PageObjects into our `test` object. + │ │ + │ └─── [PAGENAME]Page.ts - The page objects. UI elements interacted with on a page are defined once to keep tests DRY and minimize test changes if layout changes. + └─── tests - our actual tests +``` + ## Updating Tests -If the layout of a page changes, then the tests may no longer be able to interact with locators. These locators are defined in the Page Objects defined in `/e2e/pages`. The Playwright framework provides multiple ways to choose elements to interact with. The recommended ones are defined in the [Playwright documentation](https://playwright.dev/docs/locators#quick-guide). +If the layout of a page changes, then the tests may no longer be able to interact with locators. These locators are defined in the Page Objects defined in `/e2e/pages`. The Playwright framework provides multiple ways to choose elements to interact with. The recomended ones are defined in the [Playwright documentation](https://playwright.dev/docs/locators#quick-guide). Any new pages will need to have new page objects created and then imported into the `basePage.ts` file following th format of existing pages. This allows all tests to reference the page. ## Output -Playwright will generate an html report with test results. This report will show all tests that were run, and will allow a user to view the results of any failed tests. This report can be viewed with the script `yarn report` from the config repo. +Playwright will generate an html report with test results. This report will show all tests that were run, and will allow a user to view the results of any failed tests. diff --git a/e2e/generateTestData.js b/e2e/generateTestData.js index 1b0477117..89c83ca9a 100644 --- a/e2e/generateTestData.js +++ b/e2e/generateTestData.js @@ -6,14 +6,11 @@ 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) { @@ -22,12 +19,11 @@ for (const story of storyPaths) { } const testDataJson = { - catalogs: catalogNames, - datasetIds: datasetIds, - stories: storyNames + "catalogs": catalogNames, + "stories": storyNames } -fs.writeFile(path.join(__dirname, '../.veda/ui/e2e/playwrightTestData.json'), JSON.stringify(testDataJson), err => { +fs.writeFile(path.join(__dirname, 'playwrightTestData.json'), JSON.stringify(testDataJson), err => { if (err) { console.error(err); } else { diff --git a/e2e/pages/aboutPage.ts b/e2e/pages/aboutPage.ts new file mode 100644 index 000000000..a9e84eac3 --- /dev/null +++ b/e2e/pages/aboutPage.ts @@ -0,0 +1,11 @@ +import { Locator, Page } from '@playwright/test'; + +export default class AboutPage { + readonly page: Page; + readonly mainContent: Locator; + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + } +} \ No newline at end of file diff --git a/e2e/pages/analysisPage.ts b/e2e/pages/analysisPage.ts new file mode 100644 index 000000000..36f267a1e --- /dev/null +++ b/e2e/pages/analysisPage.ts @@ -0,0 +1,55 @@ +import { Locator, Page, test } from '@playwright/test'; + +export default class AnalysisPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + readonly mapboxCanvas: Locator; + readonly generateAnalysisButton: Locator; + readonly datasetOptions: Locator; + readonly datasetCheckbox: Locator; + + + constructor(page: Page) { + this.page = page; + 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'); + } + + async drawPolygon (polygonCorners: number[][]) { + await test.step('draw polygon on mapbox canvas box', async () => { + if(polygonCorners.length < 3) { + throw new Error('polygon in drawPolygon must have >=3 corners') + } + // mutating corners array to have all but the final corner + const finalCorner = polygonCorners.pop()|| []; + + // single click each remaining corner + for (const corner of polygonCorners) { + await this.mapboxCanvas.click({ + position: { + x: corner[0], + y: corner[1] + } + }); + } + // double click on final corner + await this.mapboxCanvas.dblclick({ + position: { + x: finalCorner[0], + y: finalCorner[1] + } + }); + }) + } + + async clickDatasetOption (index: number) { + test.step(`clicking dataset number ${index}`, async () => { + this.datasetCheckbox.nth(index).locator('..').click(); + }) + } +} \ No newline at end of file diff --git a/e2e/pages/analysisResultsPage.ts b/e2e/pages/analysisResultsPage.ts new file mode 100644 index 000000000..b018735d7 --- /dev/null +++ b/e2e/pages/analysisResultsPage.ts @@ -0,0 +1,13 @@ +import { Locator, Page } from '@playwright/test'; + +export default class AnalysisResultsPage { + readonly page: Page; + readonly analysisCards: Locator; + + + constructor(page: Page) { + this.page = page; + this.analysisCards = this.page.getByTestId('analysisCards'); + } + +} \ No newline at end of file diff --git a/e2e/pages/basePage.ts b/e2e/pages/basePage.ts new file mode 100644 index 000000000..b2dde30a1 --- /dev/null +++ b/e2e/pages/basePage.ts @@ -0,0 +1,52 @@ +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 StoryPage from './storyPage'; + +export const test = base.extend<{ + aboutPage: AboutPage; + analysisPage: AnalysisPage; + analysisResultsPage: AnalysisResultsPage; + footerComponent: FooterComponent; + headerComponent: HeaderComponent; + homePage: HomePage; + catalogPage: CatalogPage; + datasetPage: DatasetPage; + storyPage: StoryPage +}> ({ + aboutPage: async ({page}, use) => { + await use(new AboutPage(page)); + }, + analysisPage: async ({page}, use) => { + await use(new AnalysisPage(page)); + }, + analysisResultsPage: async ({page}, use) => { + await use(new AnalysisResultsPage(page)); + }, + catalogPage: async ({page}, use) => { + await use(new CatalogPage(page)); + }, + datasetPage: async ({page}, use) => { + await use(new DatasetPage(page)); + }, + homePage: async ({page}, use) => { + await use(new HomePage(page)); + }, + storyPage: async ({page}, use) => { + await use(new StoryPage(page)); + }, + headerComponent: async ({page}, use) => { + await use(new HeaderComponent(page)); + }, + footerComponent: async ({page}, use) => { + await use(new FooterComponent(page)); + }, +}); + +export const expect = test.expect; \ No newline at end of file diff --git a/e2e/pages/catalogPage.ts b/e2e/pages/catalogPage.ts new file mode 100644 index 000000000..64025f5df --- /dev/null +++ b/e2e/pages/catalogPage.ts @@ -0,0 +1,14 @@ +import { Locator, Page } from '@playwright/test'; + +export default class CatalogPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.header = this.mainContent.getByRole('heading', {level: 1}) + } +} \ No newline at end of file diff --git a/e2e/pages/datasetPage.ts b/e2e/pages/datasetPage.ts new file mode 100644 index 000000000..75386bca9 --- /dev/null +++ b/e2e/pages/datasetPage.ts @@ -0,0 +1,18 @@ +import { Locator, Page } from '@playwright/test'; + +export default class DatasetPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + readonly exploreDataButton: Locator; + readonly analyzeDataButton: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.header = this.mainContent.getByRole('heading', { level: 1 }) + this.exploreDataButton = this.page.getByRole('link', {name: /explore data/i} ); + this.analyzeDataButton = this.page.getByRole('button', {name: /analyze data/i} ); + } +} \ No newline at end of file diff --git a/e2e/pages/footerComponent.ts b/e2e/pages/footerComponent.ts new file mode 100644 index 000000000..90d5bdf8d --- /dev/null +++ b/e2e/pages/footerComponent.ts @@ -0,0 +1,13 @@ +import { Locator, Page } from '@playwright/test'; + +export default class FooterComponent { + readonly page: Page; + readonly footer: Locator; + readonly partners: Locator; + + constructor(page: Page) { + this.page = page; + this.footer = this.page.locator('footer'); + this.partners = this.footer.locator('div'); + } +} \ No newline at end of file diff --git a/e2e/pages/headerComponent.ts b/e2e/pages/headerComponent.ts new file mode 100644 index 000000000..96808702a --- /dev/null +++ b/e2e/pages/headerComponent.ts @@ -0,0 +1,23 @@ +import { Locator, Page } from '@playwright/test'; + +export default class HeaderComponent { + readonly page: Page; + readonly header: Locator; + readonly welcomeLink: Locator; + readonly dataCatalogLink: Locator; + readonly analysisLink: Locator; + readonly dataInsightsLink: Locator; + readonly aboutLink: Locator; + readonly feedbackLink: Locator; + + constructor(page: Page) { + this.page = page; + this.header = this.page.getByRole('navigation'); + this.welcomeLink = this.header.getByRole('link', {name: /welcome/i}); + this.dataCatalogLink = this.header.getByRole('link', {name: / data catalog/i}); + this.analysisLink = this.header.getByRole('link', {name: /analysis/i}); + this.dataInsightsLink = this.header.getByRole('link', {name: /data insights/i}); + this.aboutLink = this.header.getByRole('link', {name: /about/i}); + this.feedbackLink = this.header.getByRole('link', {name: /feedback/i}); + } +} \ No newline at end of file diff --git a/e2e/pages/homePage.ts b/e2e/pages/homePage.ts new file mode 100644 index 000000000..5fa13895e --- /dev/null +++ b/e2e/pages/homePage.ts @@ -0,0 +1,14 @@ +import { Locator, Page } from '@playwright/test'; + +export default class HomePage { + readonly page: Page; + readonly mainContent: Locator; + readonly headingContainer: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.headingContainer = this.mainContent.locator('div').filter({ hasText: 'U.S. Greenhouse Gas CenterUniting Data and Technology to Empower Tomorrow\'s' }).nth(2) + } +} \ No newline at end of file diff --git a/e2e/pages/storyPage.ts b/e2e/pages/storyPage.ts new file mode 100644 index 000000000..358b2caa2 --- /dev/null +++ b/e2e/pages/storyPage.ts @@ -0,0 +1,14 @@ +import { Locator, Page } from '@playwright/test'; + +export default class StoryPage { + readonly page: Page; + readonly mainContent: Locator; + readonly header: Locator; + + + constructor(page: Page) { + this.page = page; + this.mainContent = this.page.getByRole('main'); + this.header = this.mainContent.getByRole('heading', {level: 1}) + } +} \ No newline at end of file diff --git a/e2e/tests/analysis.spec.ts b/e2e/tests/analysis.spec.ts new file mode 100644 index 000000000..988c646fe --- /dev/null +++ b/e2e/tests/analysis.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from '../pages/basePage'; + +test('load /analysis route', async ({ + page, + analysisPage, + analysisResultsPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${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(); + + 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]; + + await analysisPage.mapboxCanvas.click(); + + await analysisPage.drawPolygon([firstCorner, secondCorner, thirdCorner]) + + await analysisPage.clickDatasetOption(1); + + const searchResponsePromise = page.waitForResponse(/\/search/i); + await analysisPage.generateAnalysisButton.click({force: true }); + + + 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(); + +}); \ No newline at end of file diff --git a/e2e/tests/catalog.spec.ts b/e2e/tests/catalog.spec.ts new file mode 100644 index 000000000..c9e7c4b33 --- /dev/null +++ b/e2e/tests/catalog.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from '../pages/basePage'; + +const catalogs = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['catalogs']; + +test('load catalogs on /data-catalog route', async ({ + page, + catalogPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/data-catalog'); + await expect(catalogPage.header, `catalog page should load`).toHaveText(/data catalog/i); + + for (const item of catalogs) { + const catalogCard = catalogPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).last(); + await catalogCard.scrollIntoViewIfNeeded(); + await expect(catalogCard, `${item} catalog card should load`).toBeVisible(); + }; + + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) + +}); \ No newline at end of file diff --git a/e2e/tests/catalogRouting.spec.ts b/e2e/tests/catalogRouting.spec.ts new file mode 100644 index 000000000..8161311d5 --- /dev/null +++ b/e2e/tests/catalogRouting.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from '../pages/basePage'; + +const catalogs = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['catalogs']; + +test.describe('catalog card routing', () => { + for (const item of catalogs) { + test(`${item} routes to dataset details page`, async({ + page, + catalogPage, + datasetPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/data-catalog'); + await expect(catalogPage.header, `catalog page should load`).toHaveText(/data catalog/i); + + const catalogCard = catalogPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).first(); + await catalogCard.scrollIntoViewIfNeeded(); + await catalogCard.click({force: true}); + + await expect(datasetPage.header.filter({ hasText: item}), `${item} page should load`).toBeVisible(); + + // 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 diff --git a/e2e/tests/stories.spec.ts b/e2e/tests/stories.spec.ts new file mode 100644 index 000000000..4b3adb0fe --- /dev/null +++ b/e2e/tests/stories.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '../pages/basePage'; + +const stories = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['stories']; + +test('load stories on /stories route', async ({ + page, + storyPage, + }) => { + + + + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/stories'); + await expect(storyPage.header, `data stories page should load`).toHaveText(/data stories/i); + + for (const item of stories) { + const storiesCard = storyPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).first(); + await storiesCard.scrollIntoViewIfNeeded(); + await expect(storiesCard, `${item} story card should load`).toBeVisible(); + }; + + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) + +}); \ No newline at end of file diff --git a/e2e/tests/storiesRouting.spec.ts b/e2e/tests/storiesRouting.spec.ts new file mode 100644 index 000000000..8336f16cf --- /dev/null +++ b/e2e/tests/storiesRouting.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from '../pages/basePage'; + +const stories = JSON.parse(require('fs').readFileSync('e2e/playwrightTestData.json', 'utf8'))['stories']; + +test.describe('stories card routing', () => { + for (const item of stories) { + test(`${item} routes to dataset details page`, async({ + page, + storyPage, + datasetPage, + }) => { + let pageErrorCalled = false; + // Log all uncaught errors to the terminal + page.on('pageerror', exception => { + console.log(`Uncaught exception: "${exception}"`); + pageErrorCalled = true; + }); + + await page.goto('/stories'); + await expect(storyPage.header, `stories page should load`).toHaveText(/data stories/i); + + const storyCard = storyPage.mainContent.getByRole('article').getByRole('heading', { level: 3, name: item, exact: true}).last(); + await storyCard.scrollIntoViewIfNeeded(); + await storyCard.click({force: true}); + await expect(datasetPage.header.filter({ hasText: item}), `${item} page should load`).toBeVisible(); + expect(pageErrorCalled, 'no javascript exceptions thrown on page').toBe(false) + }) + } + +}); \ No newline at end of file diff --git a/package.json b/package.json index 15291e0ae..83e01e6ba 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "local-cms": "npx netlify-cms-proxy-server", "test": "NODE_ENV=test .veda/veda test", "pretest:e2e": "node e2e/generateTestData.js", - "test:e2e": "start-server-and-test 'yarn serve' http://127.0.0.1:9000/ 'cd .veda/ui && yarn playwright test'", - "report": "cd .veda/ui && yarn playwright show-report" + "test:e2e": "yarn playwright test" }, "browserslist": "> 0.5%, last 2 versions, not dead", "engines": { @@ -23,12 +22,12 @@ "devDependencies": { "@parcel/packager-raw-url": "2.7.0", "@parcel/transformer-webmanifest": "2.7.0", + "@playwright/test": "^1.41.1", "@types/node": "^20.11.6", "dotenv": "^10.0.0", "fast-glob": "^3.3.2", "gray-matter": "^4.0.3", - "netlify-cms-proxy-server": "^1.3.24", - "start-server-and-test": "^2.0.3" + "netlify-cms-proxy-server": "^1.3.24" }, "alias": { "react": "./.veda/ui/node_modules/react", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..08dd56875 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,58 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + // Global single test timeout + timeout: 300000, + // For expect calls + expect: { + timeout: 180000, + }, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : 3, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:9000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'retain-on-failure', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + // uncomment to also run tests in Firefox and webkit + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn serve', + url: 'http://localhost:9000', + reuseExistingServer: !process.env.CI, + }, +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 44b5fb96c..6ec4d6e7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,7 +28,7 @@ resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== -"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": +"@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== @@ -49,7 +49,7 @@ resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== -"@hapi/topo@^5.0.0", "@hapi/topo@^5.1.0": +"@hapi/topo@^5.0.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== @@ -334,22 +334,12 @@ chrome-trace-event "^1.0.2" nullthrows "^1.1.1" -"@sideway/address@^4.1.5": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" - integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== +"@playwright/test@^1.41.1": + version "1.41.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.41.1.tgz#6954139ed4a67999f1b17460aa3d184f4b334f18" + integrity sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw== dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + playwright "1.41.1" "@types/node@^20.11.6": version "20.11.6" @@ -373,11 +363,6 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -arg@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -402,20 +387,6 @@ async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios@^1.6.1: - version "1.6.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" - integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== - dependencies: - follow-redirects "^1.15.4" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" @@ -423,11 +394,6 @@ basic-auth@~2.0.1: dependencies: safe-buffer "5.1.2" -bluebird@3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -474,11 +440,6 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -check-more-types@2.24.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" - integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== - chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -532,13 +493,6 @@ colorspace@1.1.x: color "^3.1.3" text-hex "1.0.x" -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -569,15 +523,6 @@ cors@^2.8.5: object-assign "^4" vary "^1" -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -585,18 +530,13 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.3.4, debug@^4.1.1, debug@^4.3.4: +debug@^4.1.1, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -617,11 +557,6 @@ dotenv@^10.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -duplexer@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" - integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -652,34 +587,6 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -event-stream@=3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" - integrity sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g== - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" - -execa@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - express@^4.17.1: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -772,20 +679,6 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -follow-redirects@^1.15.4: - version "1.15.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" - integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -796,10 +689,10 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -from@~0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g== +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" @@ -815,11 +708,6 @@ get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.3" -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -865,11 +753,6 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -919,22 +802,6 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -joi@^17.11.0: - version "17.12.1" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.1.tgz#3347ecf4cd3301962d42191c021b165eef1f395b" - integrity sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ== - dependencies: - "@hapi/hoek" "^9.3.0" - "@hapi/topo" "^5.1.0" - "@sideway/address" "^4.1.5" - "@sideway/formula" "^3.0.1" - "@sideway/pinpoint" "^2.0.0" - js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -958,11 +825,6 @@ kuler@^2.0.0: resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== -lazy-ass@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" - integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== - lmdb@2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.5.2.tgz#37e28a9fb43405f4dc48c44cec0e13a14c4a6ff1" @@ -981,11 +843,6 @@ lmdb@2.5.2: "@lmdb/lmdb-linux-x64" "2.5.2" "@lmdb/lmdb-win32-x64" "2.5.2" -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - logform@^2.3.2, logform@^2.4.0: version "2.4.2" resolved "https://registry.yarnpkg.com/logform/-/logform-2.4.2.tgz#a617983ac0334d0c3b942c34945380062795b47c" @@ -997,11 +854,6 @@ logform@^2.3.2, logform@^2.4.0: safe-stable-stringify "^2.3.1" triple-beam "^1.3.0" -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" - integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -1012,11 +864,6 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1040,7 +887,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -1052,16 +899,6 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimist@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - morgan@^1.9.1: version "1.10.0" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" @@ -1154,13 +991,6 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -1202,13 +1032,6 @@ one-time@^1.0.0: dependencies: fn.name "1.x.x" -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - ordered-binary@^1.2.4: version "1.4.0" resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.4.0.tgz#6bb53d44925f3b8afc33d1eed0fa15693b211389" @@ -1219,28 +1042,30 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -pause-stream@0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A== - dependencies: - through "~2.3" - picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +playwright-core@1.41.1: + version "1.41.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.41.1.tgz#9c152670010d9d6f970f34b68e3e935d3c487431" + integrity sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg== + +playwright@1.41.1: + version "1.41.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.41.1.tgz#83325f34165840d019355c2a78a50f21ed9b9c85" + integrity sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ== + dependencies: + playwright-core "1.41.1" + optionalDependencies: + fsevents "2.3.2" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -1249,18 +1074,6 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -ps-tree@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd" - integrity sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA== - dependencies: - event-stream "=3.3.4" - qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -1309,13 +1122,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.8.1: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -1383,18 +1189,6 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" @@ -1404,11 +1198,6 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - simple-git@^3.0.0: version "3.15.1" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.15.1.tgz#57f595682cb0c2475d5056da078a05c8715a25ef" @@ -1425,13 +1214,6 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -split@0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - integrity sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA== - dependencies: - through "2" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -1442,32 +1224,11 @@ stack-trace@0.0.x: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== -start-server-and-test@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-2.0.3.tgz#15c53c85e23cba7698b498b8a2598cab95f3f802" - integrity sha512-QsVObjfjFZKJE6CS6bSKNwWZCKBG6975/jKRPPGFfFh+yOQglSeGXiNWjzgQNXdphcBI9nXbyso9tPfX4YAUhg== - dependencies: - arg "^5.0.2" - bluebird "3.7.2" - check-more-types "2.24.0" - debug "4.3.4" - execa "5.1.1" - lazy-ass "1.6.0" - ps-tree "1.2.0" - wait-on "7.2.0" - statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" - integrity sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw== - dependencies: - duplexer "~0.1.1" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1480,11 +1241,6 @@ strip-bom-string@^1.0.0: resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" integrity sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g== -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -1497,11 +1253,6 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== -through@2, through@~2.3, through@~2.3.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -1519,11 +1270,6 @@ triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== -tslib@^2.1.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== - tslib@^2.3.1: version "2.4.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" @@ -1567,17 +1313,6 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -wait-on@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-7.2.0.tgz#d76b20ed3fc1e2bebc051fae5c1ff93be7892928" - integrity sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ== - dependencies: - axios "^1.6.1" - joi "^17.11.0" - lodash "^4.17.21" - minimist "^1.2.8" - rxjs "^7.8.1" - weak-lru-cache@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz#fdbb6741f36bae9540d12f480ce8254060dccd19" @@ -1588,13 +1323,6 @@ what-the-diff@^0.6.0: resolved "https://registry.yarnpkg.com/what-the-diff/-/what-the-diff-0.6.0.tgz#445cc56a9d8ee9aea0ee1ed943f4957ae009291e" integrity sha512-8BgQ4uo4cxojRXvCIcqDpH4QHaq0Ksn2P3LYfztylC5LDSwZKuGHf0Wf7sAStjPLTcB8eCB8pJJcPQSWfhZlkg== -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - winston-transport@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.5.0.tgz#6e7b0dd04d393171ed5e4e4905db265f7ab384fa" From f9fa9288e055557e2c8c5023cf09b546affb76c5 Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Fri, 1 Mar 2024 16:14:56 -0700 Subject: [PATCH 5/9] 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 From 504fd72e39096bca85326b27ffc18c8b25c3a884 Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Fri, 1 Mar 2024 16:30:51 -0700 Subject: [PATCH 6/9] remove playwright github workflow --- .github/workflows/playwright.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/playwright.yml diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml deleted file mode 100644 index a9d6b0841..000000000 --- a/.github/workflows/playwright.yml +++ /dev/null @@ -1,31 +0,0 @@ -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 From 4d3099f779dfe11e100c76195e684589facd5f63 Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Fri, 27 Sep 2024 12:03:53 -0600 Subject: [PATCH 7/9] update tests to handle updates of config on dev --- e2e/pages/analysisPage.ts | 59 -------------------- e2e/pages/analysisResultsPage.ts | 13 ----- e2e/pages/basePage.ts | 10 ---- e2e/pages/explorePage.ts | 4 +- e2e/tests/analysis.spec.ts | 91 ------------------------------- e2e/tests/exploreDatasets.spec.ts | 16 +++--- 6 files changed, 10 insertions(+), 183 deletions(-) delete mode 100644 e2e/pages/analysisPage.ts delete mode 100644 e2e/pages/analysisResultsPage.ts delete mode 100644 e2e/tests/analysis.spec.ts diff --git a/e2e/pages/analysisPage.ts b/e2e/pages/analysisPage.ts deleted file mode 100644 index f1a658bf1..000000000 --- a/e2e/pages/analysisPage.ts +++ /dev/null @@ -1,59 +0,0 @@ -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 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.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[][]) { - await test.step('draw polygon on mapbox canvas box', async () => { - if(polygonCorners.length < 3) { - throw new Error('polygon in drawPolygon must have >=3 corners') - } - // mutating corners array to have all but the final corner - const finalCorner = polygonCorners.pop()|| []; - - // single click each remaining corner - for (const corner of polygonCorners) { - await this.mapboxCanvas.click({ - position: { - x: corner[0], - y: corner[1] - } - }); - } - // double click on final corner - await this.mapboxCanvas.dblclick({ - position: { - x: finalCorner[0], - y: finalCorner[1] - } - }); - }) - } - - async clickDatasetOption (index: number) { - test.step(`clicking dataset number ${index}`, async () => { - this.datasetCheckbox.nth(index).click(); - }) - } -} \ No newline at end of file diff --git a/e2e/pages/analysisResultsPage.ts b/e2e/pages/analysisResultsPage.ts deleted file mode 100644 index 076b26ba8..000000000 --- a/e2e/pages/analysisResultsPage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export default class AnalysisResultsPage { - readonly page: Page; - readonly analysisCards: Locator; - - - constructor(page: Page) { - this.page = page; - 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 a2e86a5f9..af0612dbe 100644 --- a/e2e/pages/basePage.ts +++ b/e2e/pages/basePage.ts @@ -1,7 +1,5 @@ import { test as base } from '@playwright/test'; import AboutPage from './aboutPage'; -import AnalysisPage from './analysisPage'; -import AnalysisResultsPage from './analysisResultsPage'; import CatalogPage from './catalogPage'; import DatasetPage from './datasetPage'; import ExplorePage from './explorePage'; @@ -12,8 +10,6 @@ import StoryPage from './storyPage'; export const test = base.extend<{ aboutPage: AboutPage; - analysisPage: AnalysisPage; - analysisResultsPage: AnalysisResultsPage; catalogPage: CatalogPage; datasetPage: DatasetPage; explorePage: ExplorePage; @@ -25,12 +21,6 @@ export const test = base.extend<{ aboutPage: async ({page}, use) => { await use(new AboutPage(page)); }, - analysisPage: async ({page}, use) => { - await use(new AnalysisPage(page)); - }, - analysisResultsPage: async ({page}, use) => { - await use(new AnalysisResultsPage(page)); - }, catalogPage: async ({page}, use) => { await use(new CatalogPage(page)); }, diff --git a/e2e/pages/explorePage.ts b/e2e/pages/explorePage.ts index 303c6d295..379a3c2bc 100644 --- a/e2e/pages/explorePage.ts +++ b/e2e/pages/explorePage.ts @@ -2,10 +2,10 @@ import { Locator, Page } from '@playwright/test'; export default class ExplorePage { readonly page: Page; - readonly layersHeading: Locator; + readonly exploreDataLink: Locator; constructor(page: Page) { this.page = page; - this.layersHeading = this.page.getByRole('heading', { name: 'Layers' }); + this.exploreDataLink = this.page.getByRole('link', { name: /Explore data/i }) } } \ No newline at end of file diff --git a/e2e/tests/analysis.spec.ts b/e2e/tests/analysis.spec.ts deleted file mode 100644 index 6699f4203..000000000 --- a/e2e/tests/analysis.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { test, expect } from '../pages/basePage'; - -test('generate analysis with polygon', 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(); - - 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 * 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, 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); - - const searchResponsePromise = page.waitForResponse(/\/search/i); - await analysisPage.generateAnalysisButton.click({force: true }); - - - 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(); - - 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 index f544f07c2..c69437aa7 100644 --- a/e2e/tests/exploreDatasets.spec.ts +++ b/e2e/tests/exploreDatasets.spec.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import { test, expect } from '../pages/basePage'; const datasetIds = JSON.parse(fs.readFileSync('e2e/playwrightTestData.json', 'utf8')).datasetIds; - +const datasetName = JSON.parse(fs.readFileSync('e2e/playwrightTestData.json', 'utf8')).catalogNames; test.describe('explore dataset', () => { for (const dataset of datasetIds) { test(`${dataset} explore page functions`, async({ @@ -17,15 +17,15 @@ test.describe('explore dataset', () => { }); //mosaic isn't hit on all datasets - const collectionsResponsePromise = page.waitForResponse(response => - response.url().includes('collections') && response.status() === 200 - ); + // const collectionsResponsePromise = page.waitForResponse(response => + // response.url().includes('collections') && response.status() === 200 + // ); - await page.goto(`data-catalog/${dataset}/explore`); - await expect(explorePage.layersHeading).toBeVisible(); + await page.goto(`data-catalog/${dataset}`); + await expect(explorePage.exploreDataLink).toBeVisible(); - const mosaicResponse = await collectionsResponsePromise; - expect(mosaicResponse.ok(), 'mapbox request should be successful').toBeTruthy(); + // 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)); From 77d0a5680a33ce3460e6d5d8bcd90868903d0484 Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Fri, 27 Sep 2024 12:25:36 -0600 Subject: [PATCH 8/9] enable playwright in github action --- .github/workflows/checks.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index d23172fc6..123d54f09 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -78,3 +78,39 @@ jobs: - name: Test run: yarn test + + playwright_test: + timeout-minutes: 60 + needs: prep + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Use Node.js ${{ env.NODE }} + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE }} + - name: Cache node_modules + uses: actions/cache@v2 + id: cache-node-modules + with: + path: | + node_modules + .veda/ui/node_modules + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} + - name: Install dependencies + run: yarn + - name: Install UI + run: ./.veda/setup + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: MAPBOX_TOKEN="${{secrets.MAPBOX_TOKEN}}" yarn test:e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 \ No newline at end of file From 8f76d0234d08c786608956dc39dc978a9cff80b2 Mon Sep 17 00:00:00 2001 From: Stephen Kilbourn Date: Fri, 27 Sep 2024 13:38:00 -0600 Subject: [PATCH 9/9] extend wait for webserver to start --- playwright.config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playwright.config.ts b/playwright.config.ts index 08dd56875..8e892f65c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -51,8 +51,10 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { + timeout: 6 * 60 * 1000, command: 'yarn serve', url: 'http://localhost:9000', reuseExistingServer: !process.env.CI, + stdout: 'pipe' }, }); \ No newline at end of file