Skip to content

chore: upgrade to Playwright 1.12 with its new test-runner #3590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/dev/test-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ main() {
cd test
# We set these environment variables because they're used in the e2e tests
# they don't have to be these values, but these are the defaults
PASSWORD=e45432jklfdsab CODE_SERVER_ADDRESS=http://localhost:8080 yarn folio --config=config.ts --reporter=list "$@"
PASSWORD=e45432jklfdsab CODE_SERVER_ADDRESS=http://localhost:8080 yarn playwright test "$@"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the two parameters there, to have all Playwright related configuration in its config file. Also playwright.config.ts is easier to understand than config.ts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! playwright.config.ts is easier to understand. Good call!

}

main "$@"
5 changes: 2 additions & 3 deletions src/node/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ export const isHashLegacyMatch = (password: string, hashPassword: string) => {
return safeCompare(hashedWithLegacy, hashPassword)
}

const passwordMethods = ["SHA256", "ARGON2", "PLAIN_TEXT"] as const
export type PasswordMethod = typeof passwordMethods[number]
export type PasswordMethod = "SHA256" | "ARGON2" | "PLAIN_TEXT"

/**
* Used to determine the password method.
Expand Down Expand Up @@ -413,7 +412,7 @@ export const isObject = <T extends object>(obj: T): obj is T => {
* we don't have to set up a `vs` alias to be able to import with types (since
* the alternative is to directly import from `out`).
*/
const enum CharCode {
enum CharCode {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Playwright's test-runner uses babel-plugin-transform-typescript which does not support const enums. See here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! 🎉

Slash = 47,
A = 65,
Z = 90,
Expand Down
76 changes: 0 additions & 76 deletions test/config.ts

This file was deleted.

12 changes: 12 additions & 0 deletions test/e2e/baseFixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { test as base } from "@playwright/test"
import { CodeServer } from "./models/CodeServer"

export const test = base.extend<{ codeServerPage: CodeServer }>({
codeServerPage: async ({ page }, use) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a custom test-fixture. This means for every test a new one gets created. Since its using the page, which uses a context, Playwright does automatically take care of closing the context.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah, that is super cool! That's fantastic! 🎉

const codeServer = new CodeServer(page)
await codeServer.navigate()
await use(codeServer)
},
})

export const expect = test.expect
14 changes: 3 additions & 11 deletions test/e2e/browser.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import { test, expect } from "@playwright/test"
import { CodeServer } from "./models/CodeServer"
import { test, expect } from "./baseFixture"

// This is a "gut-check" test to make sure playwright is working as expected
test.describe("browser", () => {
let codeServer: CodeServer

test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.navigate()
})

test("browser should display correct userAgent", async ({ page, browserName }) => {
test("browser should display correct userAgent", async ({ codeServerPage, browserName }) => {
const displayNames = {
chromium: "Chrome",
firefox: "Firefox",
webkit: "Safari",
}
const userAgent = await page.evaluate("navigator.userAgent")
const userAgent = await codeServerPage.page.evaluate("navigator.userAgent")

expect(userAgent).toContain(displayNames[browserName])
})
Expand Down
43 changes: 13 additions & 30 deletions test/e2e/codeServer.test.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,32 @@
import { test, expect } from "@playwright/test"
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants"
import { CodeServer } from "./models/CodeServer"
import { CODE_SERVER_ADDRESS, storageState } from "../utils/constants"
import { test, expect } from "./baseFixture"

test.describe("CodeServer", () => {
// Create a new context with the saved storage state
// so we don't have to logged in
const options: any = {}
let codeServer: CodeServer

// TODO@jsjoeio
// Fix this once https://github.com/microsoft/playwright-test/issues/240
// is fixed
if (STORAGE) {
const storageState = JSON.parse(STORAGE) || {}
options.contextOptions = {
storageState,
}
}

test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.setup()
test.use({
storageState,
})

test(`should navigate to ${CODE_SERVER_ADDRESS}`, options, async ({ page }) => {
test(`should navigate to ${CODE_SERVER_ADDRESS}`, async ({ codeServerPage }) => {
// We navigate codeServer before each test
// and we start the test with a storage state
// which means we should be logged in
// so it should be on the address
const url = page.url()
const url = codeServerPage.page.url()
// We use match because there may be a / at the end
// so we don't want it to fail if we expect http://localhost:8080 to match http://localhost:8080/
expect(url).toMatch(CODE_SERVER_ADDRESS)
})

test("should always see the code-server editor", options, async ({ page }) => {
expect(await codeServer.isEditorVisible()).toBe(true)
test("should always see the code-server editor", async ({ codeServerPage }) => {
expect(await codeServerPage.isEditorVisible()).toBe(true)
})

test("should always have a connection", options, async ({ page }) => {
expect(await codeServer.isConnected()).toBe(true)
test("should always have a connection", async ({ codeServerPage }) => {
expect(await codeServerPage.isConnected()).toBe(true)
})

test("should show the Integrated Terminal", options, async ({ page }) => {
await codeServer.focusTerminal()
expect(await page.isVisible("#terminal")).toBe(true)
test("should show the Integrated Terminal", async ({ codeServerPage }) => {
await codeServerPage.focusTerminal()
expect(await codeServerPage.page.isVisible("#terminal")).toBe(true)
})
})
31 changes: 8 additions & 23 deletions test/e2e/globalSetup.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
import { test, expect } from "@playwright/test"
import { STORAGE } from "../utils/constants"
import { CodeServer } from "./models/CodeServer"
import { storageState } from "../utils/constants"
import { test, expect } from "./baseFixture"

// This test is to make sure the globalSetup works as expected
// meaning globalSetup ran and stored the storageState in STORAGE
// meaning globalSetup ran and stored the storageState
test.describe("globalSetup", () => {
// Create a new context with the saved storage state
// so we don't have to logged in
const options: any = {}
let codeServer: CodeServer

// TODO@jsjoeio
// Fix this once https://github.com/microsoft/playwright-test/issues/240
// is fixed
if (STORAGE) {
const storageState = JSON.parse(STORAGE) || {}
options.contextOptions = {
storageState,
}
}
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.setup()
test.use({
storageState,
})
test("should keep us logged in using the storageState", options, async ({ page }) => {

test("should keep us logged in using the storageState", async ({ codeServerPage }) => {
// Make sure the editor actually loaded
expect(await codeServer.isEditorVisible()).toBe(true)
expect(await codeServerPage.isEditorVisible()).toBe(true)
})
})
65 changes: 28 additions & 37 deletions test/e2e/login.test.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,67 @@
import { test, expect } from "@playwright/test"
import { PASSWORD } from "../utils/constants"
import { CodeServer } from "./models/CodeServer"
import { test, expect } from "./baseFixture"

test.describe("login", () => {
// Reset the browser so no cookies are persisted
// by emptying the storageState
const options = {
contextOptions: {
storageState: {},
},
}
let codeServer: CodeServer

test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.navigate()
test.use({
storageState: {},
})

test("should see the login page", options, async ({ page }) => {
test("should see the login page", async ({ codeServerPage }) => {
// It should send us to the login page
expect(await page.title()).toBe("code-server login")
expect(await codeServerPage.page.title()).toBe("code-server login")
})

test("should be able to login", options, async ({ page }) => {
test("should be able to login", async ({ codeServerPage }) => {
// Type in password
await page.fill(".password", PASSWORD)
await codeServerPage.page.fill(".password", PASSWORD)
// Click the submit button and login
await page.click(".submit")
await page.waitForLoadState("networkidle")
await codeServerPage.page.click(".submit")
await codeServerPage.page.waitForLoadState("networkidle")
// We do this because occassionally code-server doesn't load on Firefox
// but loads if you reload once or twice
await codeServer.reloadUntilEditorIsReady()
await codeServerPage.reloadUntilEditorIsReady()
// Make sure the editor actually loaded
expect(await codeServer.isEditorVisible()).toBe(true)
expect(await codeServerPage.isEditorVisible()).toBe(true)
})

test("should see an error message for missing password", options, async ({ page }) => {
test("should see an error message for missing password", async ({ codeServerPage }) => {
// Skip entering password
// Click the submit button and login
await page.click(".submit")
await page.waitForLoadState("networkidle")
expect(await page.isVisible("text=Missing password"))
await codeServerPage.page.click(".submit")
await codeServerPage.page.waitForLoadState("networkidle")
expect(await codeServerPage.page.isVisible("text=Missing password"))
})

test("should see an error message for incorrect password", options, async ({ page }) => {
test("should see an error message for incorrect password", async ({ codeServerPage }) => {
// Type in password
await page.fill(".password", "password123")
await codeServerPage.page.fill(".password", "password123")
// Click the submit button and login
await page.click(".submit")
await page.waitForLoadState("networkidle")
expect(await page.isVisible("text=Incorrect password"))
await codeServerPage.page.click(".submit")
await codeServerPage.page.waitForLoadState("networkidle")
expect(await codeServerPage.page.isVisible("text=Incorrect password"))
})

test("should hit the rate limiter for too many unsuccessful logins", options, async ({ page }) => {
test("should hit the rate limiter for too many unsuccessful logins", async ({ codeServerPage }) => {
// Type in password
await page.fill(".password", "password123")
await codeServerPage.page.fill(".password", "password123")
// Click the submit button and login
// The current RateLimiter allows 2 logins per minute plus
// 12 logins per hour for a total of 14
// See: src/node/routes/login.ts
for (let i = 1; i <= 14; i++) {
await page.click(".submit")
await page.waitForLoadState("networkidle")
await codeServerPage.page.click(".submit")
await codeServerPage.page.waitForLoadState("networkidle")
// We double-check that the correct error message shows
// which should be for incorrect password
expect(await page.isVisible("text=Incorrect password"))
expect(await codeServerPage.page.isVisible("text=Incorrect password"))
}

// The 15th should fail for a different reason:
// login rate
await page.click(".submit")
await page.waitForLoadState("networkidle")
expect(await page.isVisible("text=Login rate limited!"))
await codeServerPage.page.click(".submit")
await codeServerPage.page.waitForLoadState("networkidle")
expect(await codeServerPage.page.isVisible("text=Login rate limited!"))
})
})
Loading