Skip to content

Commit 97fbbfa

Browse files
Merge pull request #3133 from cdr/jsjoeio/migrate-to-playwright-test
refactor(testing): migrate to playwright-test from jest-playwright
2 parents 73e316e + 450fcd5 commit 97fbbfa

13 files changed

+1294
-731
lines changed

.github/workflows/ci.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,11 @@ jobs:
350350
if: always()
351351
uses: actions/upload-artifact@v2
352352
with:
353-
name: test-videos
354-
path: ./test/e2e/videos
353+
name: failed-test-videos
354+
path: ./test/test-results
355355

356356
- name: Remove release packages and test artifacts
357-
run: rm -rf ./release-packages ./test/e2e/videos
357+
run: rm -rf ./release-packages ./test/test-results
358358

359359
docker-amd64:
360360
runs-on: ubuntu-latest

.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ node-*
1616
.home
1717
coverage
1818
**/.DS_Store
19-
test/e2e/videos
20-
test/e2e/screenshots
19+
# Failed e2e test videos are saved here
20+
test/test-results

ci/dev/test-e2e.sh

+4-13
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,10 @@ set -euo pipefail
33

44
main() {
55
cd "$(dirname "$0")/../.."
6-
# We must keep jest in a sub-directory. See ../../test/package.json for more
7-
# information. We must also run it from the root otherwise coverage will not
8-
# include our source files.
9-
if [[ -z ${PASSWORD-} ]] || [[ -z ${CODE_SERVER_ADDRESS-} ]]; then
10-
echo "The end-to-end testing suites rely on your local environment"
11-
echo -e "\n"
12-
echo "Please set the following environment variables locally:"
13-
echo " \$PASSWORD"
14-
echo " \$CODE_SERVER_ADDRESS"
15-
echo -e "\n"
16-
exit 1
17-
fi
18-
CS_DISABLE_PLUGINS=true ./test/node_modules/.bin/jest "$@" --config ./test/jest.e2e.config.ts --runInBand
6+
cd test
7+
# We set these environment variables because they're used in the e2e tests
8+
# they don't have to be these values, but these are the defaults
9+
PASSWORD=e45432jklfdsab CODE_SERVER_ADDRESS=http://localhost:8080 yarn folio --config=config.ts --reporter=list "$@"
1910
}
2011

2112
main "$@"

test/config.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {
2+
ChromiumEnv,
3+
FirefoxEnv,
4+
WebKitEnv,
5+
test,
6+
setConfig,
7+
PlaywrightOptions,
8+
Config,
9+
globalSetup,
10+
} from "@playwright/test"
11+
import * as crypto from "crypto"
12+
import path from "path"
13+
import { PASSWORD } from "./utils/constants"
14+
import * as wtfnode from "./utils/wtfnode"
15+
16+
// Playwright doesn't like that ../src/node/util has an enum in it
17+
// so I had to copy hash in separately
18+
const hash = (str: string): string => {
19+
return crypto.createHash("sha256").update(str).digest("hex")
20+
}
21+
22+
const cookieToStore = {
23+
sameSite: "Lax" as const,
24+
name: "key",
25+
value: hash(PASSWORD),
26+
domain: "localhost",
27+
path: "/",
28+
expires: -1,
29+
httpOnly: false,
30+
secure: false,
31+
}
32+
33+
globalSetup(async () => {
34+
console.log("\n🚨 Running globalSetup for playwright end-to-end tests")
35+
console.log("👋 Please hang tight...")
36+
37+
if (process.env.WTF_NODE) {
38+
wtfnode.setup()
39+
}
40+
41+
const storage = {
42+
cookies: [cookieToStore],
43+
}
44+
45+
// Save storage state and store as an env variable
46+
// More info: https://playwright.dev/docs/auth?_highlight=authe#reuse-authentication-state
47+
process.env.STORAGE = JSON.stringify(storage)
48+
console.log("✅ globalSetup is now complete.")
49+
})
50+
51+
const config: Config = {
52+
testDir: path.join(__dirname, "e2e"), // Search for tests in this directory.
53+
timeout: 30000, // Each test is given 30 seconds.
54+
retries: 3, // Retry failing tests 2 times
55+
}
56+
57+
if (process.env.CI) {
58+
// In CI, retry failing tests 2 times
59+
// in the event of flakiness
60+
config.retries = 2
61+
}
62+
63+
setConfig(config)
64+
65+
const options: PlaywrightOptions = {
66+
headless: true, // Run tests in headless browsers.
67+
video: "retain-on-failure",
68+
}
69+
70+
// Run tests in three browsers.
71+
test.runWith(new ChromiumEnv(options), { tag: "chromium" })
72+
test.runWith(new FirefoxEnv(options), { tag: "firefox" })
73+
test.runWith(new WebKitEnv(options), { tag: "webkit" })

test/e2e/browser.test.ts

+14-34
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,15 @@
1-
/// <reference types="jest-playwright-preset" />
2-
3-
// This test is for nothing more than to make sure
4-
// tests are running in multiple browsers
5-
describe("Browser gutcheck", () => {
6-
beforeEach(async () => {
7-
await jestPlaywright.resetBrowser({
8-
logger: {
9-
isEnabled: (name) => name === "browser",
10-
log: (name, severity, message, args) => console.log(`${name} ${message}`),
11-
},
12-
})
13-
})
14-
15-
test("should display correct browser based on userAgent", async () => {
16-
const displayNames = {
17-
chromium: "Chrome",
18-
firefox: "Firefox",
19-
webkit: "Safari",
20-
}
21-
const userAgent = await page.evaluate("navigator.userAgent")
22-
23-
if (browserName === "chromium") {
24-
expect(userAgent).toContain(displayNames[browserName])
25-
}
26-
27-
if (browserName === "firefox") {
28-
expect(userAgent).toContain(displayNames[browserName])
29-
}
30-
31-
if (browserName === "webkit") {
32-
expect(userAgent).toContain(displayNames[browserName])
33-
}
34-
})
1+
import { test, expect } from "@playwright/test"
2+
import { CODE_SERVER_ADDRESS } from "../utils/constants"
3+
4+
// This is a "gut-check" test to make sure playwright is working as expected
5+
test("browser should display correct userAgent", async ({ page, browserName }) => {
6+
const displayNames = {
7+
chromium: "Chrome",
8+
firefox: "Firefox",
9+
webkit: "Safari",
10+
}
11+
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
12+
const userAgent = await page.evaluate("navigator.userAgent")
13+
14+
expect(userAgent).toContain(displayNames[browserName])
3515
})

test/e2e/globalSetup.test.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
/// <reference types="jest-playwright-preset" />
1+
import { test, expect } from "@playwright/test"
22
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants"
33

44
// This test is to make sure the globalSetup works as expected
55
// meaning globalSetup ran and stored the storageState in STORAGE
6-
describe("globalSetup", () => {
7-
beforeEach(async () => {
8-
// Create a new context with the saved storage state
9-
// so we don't have to logged in
6+
test.describe("globalSetup", () => {
7+
// Create a new context with the saved storage state
8+
// so we don't have to logged in
9+
const options: any = {}
10+
11+
// TODO@jsjoeio
12+
// Fix this once https://github.com/microsoft/playwright-test/issues/240
13+
// is fixed
14+
if (STORAGE) {
1015
const storageState = JSON.parse(STORAGE) || {}
11-
await jestPlaywright.resetContext({
16+
options.contextOptions = {
1217
storageState,
13-
})
18+
}
19+
}
20+
test("should keep us logged in using the storageState", options, async ({ page }) => {
1421
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
15-
})
16-
17-
it("should keep us logged in using the storageState", async () => {
1822
// Make sure the editor actually loaded
1923
expect(await page.isVisible("div.monaco-workbench"))
2024
})

test/e2e/login.test.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1-
/// <reference types="jest-playwright-preset" />
1+
import { test, expect } from "@playwright/test"
22
import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants"
33

4-
describe("login", () => {
5-
beforeEach(async () => {
6-
await jestPlaywright.resetBrowser()
7-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
8-
})
4+
test.describe("login", () => {
5+
// Reset the browser so no cookies are persisted
6+
// by emptying the storageState
7+
const options = {
8+
contextOptions: {
9+
storageState: {},
10+
},
11+
}
912

10-
it("should be able to login", async () => {
13+
test("should be able to login", options, async ({ page }) => {
14+
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
1115
// Type in password
1216
await page.fill(".password", PASSWORD)
1317
// Click the submit button and login

test/e2e/loginPage.test.ts

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1-
/// <reference types="jest-playwright-preset" />
2-
1+
import { test, expect } from "@playwright/test"
32
import { CODE_SERVER_ADDRESS } from "../utils/constants"
43

5-
describe("login page", () => {
6-
beforeEach(async () => {
7-
await jestPlaywright.resetContext({
8-
logger: {
9-
isEnabled: (name, severity) => name === "browser",
10-
log: (name, severity, message, args) => console.log(`${name} ${message}`),
11-
},
12-
})
13-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
14-
})
4+
test.describe("login page", () => {
5+
// Reset the browser so no cookies are persisted
6+
// by emptying the storageState
7+
const options = {
8+
contextOptions: {
9+
storageState: {},
10+
},
11+
}
1512

16-
it("should see the login page", async () => {
13+
test("should see the login page", options, async ({ page }) => {
14+
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
1715
// It should send us to the login page
1816
expect(await page.title()).toBe("code-server login")
1917
})

test/e2e/logout.test.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
/// <reference types="jest-playwright-preset" />
1+
import { test, expect } from "@playwright/test"
22
import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants"
33

4-
describe("logout", () => {
5-
beforeEach(async () => {
6-
await jestPlaywright.resetBrowser()
4+
test.describe("logout", () => {
5+
// Reset the browser so no cookies are persisted
6+
// by emptying the storageState
7+
const options = {
8+
contextOptions: {
9+
storageState: {},
10+
},
11+
}
12+
test("should be able login and logout", options, async ({ page }) => {
713
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
8-
})
9-
10-
it("should be able login and logout", async () => {
1114
// Type in password
1215
await page.fill(".password", PASSWORD)
1316
// Click the submit button and login

test/e2e/openHelpAbout.test.ts

+36-28
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,46 @@
1-
/// <reference types="jest-playwright-preset" />
1+
import { test, expect } from "@playwright/test"
22
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants"
33

4-
describe("Open Help > About", () => {
5-
beforeEach(async () => {
6-
// Create a new context with the saved storage state
7-
// so we don't have to logged in
4+
test.describe("Open Help > About", () => {
5+
// Create a new context with the saved storage state
6+
// so we don't have to logged in
7+
const options: any = {}
8+
// TODO@jsjoeio
9+
// Fix this once https://github.com/microsoft/playwright-test/issues/240
10+
// is fixed
11+
if (STORAGE) {
812
const storageState = JSON.parse(STORAGE) || {}
9-
await jestPlaywright.resetContext({
13+
options.contextOptions = {
1014
storageState,
11-
})
12-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
13-
})
15+
}
16+
}
1417

15-
it("should see a 'Help' then 'About' button in the Application Menu that opens a dialog", async () => {
16-
// Make sure the editor actually loaded
17-
expect(await page.isVisible("div.monaco-workbench"))
18+
test(
19+
"should see a 'Help' then 'About' button in the Application Menu that opens a dialog",
20+
options,
21+
async ({ page }) => {
22+
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
23+
// Make sure the editor actually loaded
24+
expect(await page.isVisible("div.monaco-workbench"))
1825

19-
// Click the Application menu
20-
await page.click("[aria-label='Application Menu']")
21-
// See the Help button
22-
const helpButton = "a.action-menu-item span[aria-label='Help']"
23-
expect(await page.isVisible(helpButton))
26+
// Click the Application menu
27+
await page.click("[aria-label='Application Menu']")
28+
// See the Help button
29+
const helpButton = "a.action-menu-item span[aria-label='Help']"
30+
expect(await page.isVisible(helpButton))
2431

25-
// Hover the helpButton
26-
await page.hover(helpButton)
32+
// Hover the helpButton
33+
await page.hover(helpButton)
2734

28-
// see the About button and click it
29-
const aboutButton = "a.action-menu-item span[aria-label='About']"
30-
expect(await page.isVisible(aboutButton))
31-
// NOTE: it won't work unless you hover it first
32-
await page.hover(aboutButton)
33-
await page.click(aboutButton)
35+
// see the About button and click it
36+
const aboutButton = "a.action-menu-item span[aria-label='About']"
37+
expect(await page.isVisible(aboutButton))
38+
// NOTE: it won't work unless you hover it first
39+
await page.hover(aboutButton)
40+
await page.click(aboutButton)
3441

35-
const codeServerText = "text=code-server"
36-
expect(await page.isVisible(codeServerText))
37-
})
42+
const codeServerText = "text=code-server"
43+
expect(await page.isVisible(codeServerText))
44+
},
45+
)
3846
})

0 commit comments

Comments
 (0)