Skip to content

Commit ecc8d7a

Browse files
authored
Refact E2E tests to use Page Object Model (POM), fixtures, and add additional tests (Netflix#2945)
* Refact E2E tests to use Page Object Model (POM), fixtures, and add additional tests * remove comment * update .gitignore to account for change in artifact directory
1 parent 0a81df7 commit ecc8d7a

15 files changed

+406
-148
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,4 @@ docker/.env
210210
/test-results/
211211
/playwright-report/
212212
/playwright/.cache/
213-
/tests/e2e/artifacts/*
213+
/tests/static/e2e/artifacts/*

playwright.config.ts

+13-15
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,21 @@ import type { PlaywrightTestConfig } from "@playwright/test"
22
import { devices } from "@playwright/test"
33

44
/**
5-
* See https://playwright.dev/docs/test-configuration.
5+
* @see https://playwright.dev/docs/test-configuration
66
*/
77
const config: PlaywrightTestConfig = {
8-
testDir: "./tests/e2e",
9-
outputDir: "./tests/e2e/artifacts/test-failures",
8+
testDir: "./tests/static/e2e",
9+
outputDir: "./tests/static/e2e/artifacts/test-failures",
10+
use: {
11+
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
12+
actionTimeout: 0,
13+
/* Base URL to use in actions like `await page.goto('/')`. */
14+
baseURL: "http://localhost:8080/",
15+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
16+
trace: "on",
17+
video: "on",
18+
screenshot: "on",
19+
},
1020
/* Maximum time one test can run for. */
1121
timeout: 100 * 1000,
1222
expect: {
@@ -26,18 +36,6 @@ const config: PlaywrightTestConfig = {
2636
workers: process.env.CI ? 1 : undefined,
2737
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
2838
reporter: "html",
29-
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
30-
use: {
31-
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
32-
actionTimeout: 0,
33-
/* Base URL to use in actions like `await page.goto('/')`. */
34-
baseURL: "http://localhost:8080/",
35-
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
36-
trace: "on",
37-
video: "on",
38-
screenshot: "on",
39-
},
40-
4139
/* Configure projects for major browsers */
4240
projects: [
4341
{

src/dispatch/static/dispatch/src/incident/Table.vue

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
:sort-by.sync="sortBy"
4040
:sort-desc.sync="descending"
4141
:loading="loading"
42+
data-testid="incident-data-table"
4243
v-model="selected"
4344
loading-text="Loading... Please wait"
4445
show-select
@@ -88,6 +89,7 @@
8889
</template>
8990
<v-list>
9091
<v-list-item
92+
data-testid="incident-table-edit"
9193
:to="{
9294
name: 'IncidentTableEdit',
9395
params: { name: item.name },

tests/e2e/report-submission.spec.ts

-106
This file was deleted.

tests/e2e/utils/login.ts

-26
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { test as base } from "@playwright/test"
2+
import { AuthPage } from "../pages/auth-page"
3+
import { ReportIncidentPage } from "../pages/report-incident-page"
4+
import { IncidentsPage } from "../pages/incidents-page"
5+
6+
type DispatchFixtures = {
7+
authPage: AuthPage
8+
reportIncidentPage: ReportIncidentPage
9+
incidentsPage: IncidentsPage
10+
}
11+
12+
export const test = base.extend<DispatchFixtures>({
13+
authPage: async ({ page }, use) => {
14+
await use(new AuthPage(page))
15+
},
16+
17+
reportIncidentPage: async ({ page }, use) => {
18+
await use(new ReportIncidentPage(page))
19+
},
20+
21+
incidentsPage: async ({ page }, use) => {
22+
const incidentsPage = new IncidentsPage(page)
23+
await use(incidentsPage)
24+
},
25+
})
26+
export { expect } from "@playwright/test"
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { test, expect } from "./fixtures/dispatch-fixtures"
2+
import register from "./utils/register"
3+
4+
test.describe("Authenticated Dispatch App", () => {
5+
test.beforeEach(async ({ authPage }) => {
6+
await register(authPage)
7+
}),
8+
test("The edit list should appear after clicking the incident edit kebab.", async ({
9+
incidentsPage,
10+
}) => {
11+
await incidentsPage.goto()
12+
await incidentsPage.EditKebab.click()
13+
await expect(incidentsPage.EditMenu).toBeVisible()
14+
await expect.soft(incidentsPage.EditMenu).toBeVisible()
15+
await expect.soft(incidentsPage.EditViewEdit).toBeVisible()
16+
await expect.soft(incidentsPage.EditCreateReport).toBeVisible()
17+
await expect.soft(incidentsPage.EditRunWorkflow).toBeVisible()
18+
await expect.soft(incidentsPage.EditDelete).toBeVisible()
19+
})
20+
})

tests/static/e2e/pages/auth-page.ts

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { expect, Locator, Page } from "@playwright/test"
2+
import { orgSlug, Routes } from "../routes"
3+
4+
export class AuthPage {
5+
readonly page: Page
6+
// Login
7+
readonly loginRoute: string
8+
readonly loginHeader: Locator
9+
// Register
10+
readonly registerRoute: string
11+
readonly registerHeader: Locator
12+
readonly registerLink: Locator
13+
readonly registerButton: Locator
14+
// Shared Components
15+
readonly emailLabel: Locator
16+
readonly passwordLabel: Locator
17+
18+
constructor(page: Page) {
19+
this.page = page
20+
// Login
21+
this.loginRoute = orgSlug + Routes.Login
22+
this.loginHeader = page.getByText("Login").first()
23+
// Register
24+
this.registerRoute = orgSlug + Routes.Register
25+
this.registerHeader = page.getByText("Register").first()
26+
this.registerLink = page.getByRole("link", { name: "Register" })
27+
this.registerButton = page.getByRole("button", { name: "Register" })
28+
// Shared Components
29+
this.emailLabel = page.getByLabel("Email")
30+
this.passwordLabel = page.getByLabel("Password")
31+
}
32+
33+
async gotoLogin() {
34+
await Promise.all([
35+
this.page.goto(this.loginRoute),
36+
await this.page.waitForURL(this.loginRoute),
37+
await expect(this.loginHeader).toBeVisible(),
38+
])
39+
}
40+
41+
async gotoRegisterWithLink() {
42+
await Promise.all([
43+
/*
44+
(wshel) Directly visiting register page will redirect the user to the login page.
45+
We must by click the register button on the login page.
46+
*/
47+
await this.gotoLogin(),
48+
await this.registerLink.first().click(),
49+
await this.page.waitForURL(this.registerRoute),
50+
await expect(this.registerHeader).toBeVisible(),
51+
])
52+
}
53+
54+
async registerNewUser(email: string, password: string) {
55+
await this.gotoRegisterWithLink()
56+
await this.emailLabel.first().click()
57+
await this.emailLabel.fill(email)
58+
59+
await this.passwordLabel.first().click()
60+
await this.passwordLabel.fill(password)
61+
62+
await Promise.all([
63+
this.registerButton.click(),
64+
this.page.waitForURL(orgSlug + Routes.Dashboards),
65+
])
66+
}
67+
68+
async pageObjectModel(email: string, password: string) {
69+
await this.registerNewUser(email, password)
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Locator, Page } from "@playwright/test"
2+
import { Routes, orgSlug } from "../routes"
3+
4+
export class IncidentsPage {
5+
readonly page: Page
6+
readonly route: string
7+
8+
readonly SaveButton: Locator
9+
readonly CloseButton: Locator
10+
11+
readonly CostsTab: Locator
12+
readonly CostsAmount: Locator
13+
14+
constructor(page: Page) {
15+
this.page = page
16+
this.route = orgSlug + Routes.Incidents
17+
18+
this.SaveButton = page.getByRole("button").filter({ hasText: "save" })
19+
this.CloseButton = page.getByRole("button", { name: "Close" })
20+
21+
this.CostsTab = page.getByRole("tab", { name: "Costs" })
22+
this.CostsAmount = page.getByLabel("Amount")
23+
}
24+
25+
async goto(incident: string) {
26+
await Promise.all([
27+
this.page.goto(this.route + `/${incident}`),
28+
await this.page.waitForURL(this.route),
29+
])
30+
}
31+
32+
async addCost(incident: string) {
33+
await this.goto(incident)
34+
await this.CostsTab.first().click()
35+
await this.CostsAmount.click()
36+
await this.CostsAmount.fill("100000")
37+
}
38+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { expect, Locator, Page } from "@playwright/test"
2+
import { Routes, orgSlug } from "../routes"
3+
4+
export class IncidentsPage {
5+
readonly page: Page
6+
readonly route: string
7+
readonly Row: Locator
8+
readonly FirstRow: Locator
9+
readonly OtherRow: Locator
10+
readonly NextPage: Locator
11+
readonly EditKebab: Locator
12+
readonly EditMenu: Locator
13+
readonly EditViewEdit: Locator
14+
readonly EditCreateReport: Locator
15+
readonly EditRunWorkflow: Locator
16+
readonly EditDelete: Locator
17+
18+
constructor(page: Page, incident: string = `dispatch-${orgSlug}-${orgSlug}-2`) {
19+
this.page = page
20+
this.route = orgSlug + Routes.Incidents
21+
this.Row = page.locator("tr")
22+
this.NextPage = page.getByRole("button", { name: "Next page" })
23+
this.EditKebab = page
24+
.getByRole("row", {
25+
name: incident,
26+
})
27+
.getByRole("button")
28+
.nth(2)
29+
this.EditMenu = page.getByTestId("incident-table-edit")
30+
this.EditViewEdit = page.getByRole("menuitem", { name: "View / Edit" })
31+
this.EditCreateReport = page.getByRole("menuitem", { name: "Create Report" })
32+
this.EditRunWorkflow = page.getByRole("menuitem", { name: "Run Workflow" })
33+
this.EditDelete = page.getByRole("menuitem", { name: "Delete" })
34+
}
35+
36+
async goto() {
37+
await Promise.all([this.page.goto(this.route), await this.page.waitForURL(this.route)])
38+
}
39+
}

0 commit comments

Comments
 (0)