-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
76935dd
commit 1d34a0f
Showing
18 changed files
with
588 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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') | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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} ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} | ||
|
||
}); |
Oops, something went wrong.