Merge pull request #3590 from mxschmitt/chore/upgrade-to-latest-playwright

chore: upgrade to Playwright 1.12 with its new test-runner
This commit is contained in:
Joe Previte 2021-06-10 11:15:41 -07:00 committed by GitHub
commit 4bb7a8ddb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 482 additions and 1043 deletions

View File

@ -6,7 +6,7 @@ main() {
cd test cd test
# We set these environment variables because they're used in the e2e tests # 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 # 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 "$@"
} }
main "$@" main "$@"

View File

@ -165,8 +165,7 @@ export const isHashLegacyMatch = (password: string, hashPassword: string) => {
return safeCompare(hashedWithLegacy, hashPassword) return safeCompare(hashedWithLegacy, hashPassword)
} }
const passwordMethods = ["SHA256", "ARGON2", "PLAIN_TEXT"] as const export type PasswordMethod = "SHA256" | "ARGON2" | "PLAIN_TEXT"
export type PasswordMethod = typeof passwordMethods[number]
/** /**
* Used to determine the password method. * Used to determine the password method.
@ -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 * 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`). * the alternative is to directly import from `out`).
*/ */
const enum CharCode { enum CharCode {
Slash = 47, Slash = 47,
A = 65, A = 65,
Z = 90, Z = 90,

View File

@ -1,76 +0,0 @@
import {
ChromiumEnv,
FirefoxEnv,
WebKitEnv,
test,
setConfig,
PlaywrightOptions,
Config,
globalSetup,
} from "@playwright/test"
import * as argon2 from "argon2"
import path from "path"
import { PASSWORD } from "./utils/constants"
import * as wtfnode from "./utils/wtfnode"
// Playwright doesn't like that ../src/node/util has an enum in it
// so I had to copy hash in separately
const hash = async (str: string): Promise<string> => {
return await argon2.hash(str)
}
const cookieToStore = {
sameSite: "Lax" as const,
name: "key",
value: "",
domain: "localhost",
path: "/",
expires: -1,
httpOnly: false,
secure: false,
}
globalSetup(async () => {
console.log("\n🚨 Running globalSetup for playwright end-to-end tests")
console.log("👋 Please hang tight...")
if (process.env.WTF_NODE) {
wtfnode.setup()
}
cookieToStore.value = await hash(PASSWORD)
const storage = {
cookies: [cookieToStore],
}
// Save storage state and store as an env variable
// More info: https://playwright.dev/docs/auth?_highlight=authe#reuse-authentication-state
process.env.STORAGE = JSON.stringify(storage)
console.log("✅ globalSetup is now complete.")
})
const config: Config = {
testDir: path.join(__dirname, "e2e"), // Search for tests in this directory.
timeout: 60000, // Each test is given 60 seconds.
retries: 3, // Retry failing tests 2 times
workers: 1,
}
if (process.env.CI) {
// In CI, retry failing tests 2 times
// in the event of flakiness
config.retries = 2
}
setConfig(config)
const options: PlaywrightOptions = {
headless: true, // Run tests in headless browsers.
video: "on",
}
// Run tests in three browsers.
test.runWith(new ChromiumEnv(options), { tag: "chromium" })
test.runWith(new FirefoxEnv(options), { tag: "firefox" })
test.runWith(new WebKitEnv(options), { tag: "webkit" })

12
test/e2e/baseFixture.ts Normal file
View File

@ -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) => {
const codeServer = new CodeServer(page)
await codeServer.navigate()
await use(codeServer)
},
})
export const expect = test.expect

View File

@ -1,22 +1,14 @@
import { test, expect } from "@playwright/test" import { test, expect } from "./baseFixture"
import { CodeServer } from "./models/CodeServer"
// This is a "gut-check" test to make sure playwright is working as expected // This is a "gut-check" test to make sure playwright is working as expected
test.describe("browser", () => { test.describe("browser", () => {
let codeServer: CodeServer test("browser should display correct userAgent", async ({ codeServerPage, browserName }) => {
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.navigate()
})
test("browser should display correct userAgent", async ({ page, browserName }) => {
const displayNames = { const displayNames = {
chromium: "Chrome", chromium: "Chrome",
firefox: "Firefox", firefox: "Firefox",
webkit: "Safari", webkit: "Safari",
} }
const userAgent = await page.evaluate("navigator.userAgent") const userAgent = await codeServerPage.page.evaluate("navigator.userAgent")
expect(userAgent).toContain(displayNames[browserName]) expect(userAgent).toContain(displayNames[browserName])
}) })

View File

@ -1,49 +1,32 @@
import { test, expect } from "@playwright/test" import { CODE_SERVER_ADDRESS, storageState } from "../utils/constants"
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants" import { test, expect } from "./baseFixture"
import { CodeServer } from "./models/CodeServer"
test.describe("CodeServer", () => { test.describe("CodeServer", () => {
// Create a new context with the saved storage state test.use({
// 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, storageState,
}
}
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.setup()
}) })
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 // We navigate codeServer before each test
// and we start the test with a storage state // and we start the test with a storage state
// which means we should be logged in // which means we should be logged in
// so it should be on the address // 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 // 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/ // 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) expect(url).toMatch(CODE_SERVER_ADDRESS)
}) })
test("should always see the code-server editor", options, async ({ page }) => { test("should always see the code-server editor", async ({ codeServerPage }) => {
expect(await codeServer.isEditorVisible()).toBe(true) expect(await codeServerPage.isEditorVisible()).toBe(true)
}) })
test("should always have a connection", options, async ({ page }) => { test("should always have a connection", async ({ codeServerPage }) => {
expect(await codeServer.isConnected()).toBe(true) expect(await codeServerPage.isConnected()).toBe(true)
}) })
test("should show the Integrated Terminal", options, async ({ page }) => { test("should show the Integrated Terminal", async ({ codeServerPage }) => {
await codeServer.focusTerminal() await codeServerPage.focusTerminal()
expect(await page.isVisible("#terminal")).toBe(true) expect(await codeServerPage.page.isVisible("#terminal")).toBe(true)
}) })
}) })

View File

@ -1,30 +1,15 @@
import { test, expect } from "@playwright/test" import { storageState } from "../utils/constants"
import { STORAGE } from "../utils/constants" import { test, expect } from "./baseFixture"
import { CodeServer } from "./models/CodeServer"
// This test is to make sure the globalSetup works as expected // 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", () => { test.describe("globalSetup", () => {
// Create a new context with the saved storage state test.use({
// 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, storageState,
}
}
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.setup()
}) })
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 // Make sure the editor actually loaded
expect(await codeServer.isEditorVisible()).toBe(true) expect(await codeServerPage.isEditorVisible()).toBe(true)
}) })
}) })

View File

@ -1,76 +1,67 @@
import { test, expect } from "@playwright/test"
import { PASSWORD } from "../utils/constants" import { PASSWORD } from "../utils/constants"
import { CodeServer } from "./models/CodeServer" import { test, expect } from "./baseFixture"
test.describe("login", () => { test.describe("login", () => {
// Reset the browser so no cookies are persisted // Reset the browser so no cookies are persisted
// by emptying the storageState // by emptying the storageState
const options = { test.use({
contextOptions: {
storageState: {}, storageState: {},
},
}
let codeServer: CodeServer
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.navigate()
}) })
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 // 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 // Type in password
await page.fill(".password", PASSWORD) await codeServerPage.page.fill(".password", PASSWORD)
// Click the submit button and login // Click the submit button and login
await page.click(".submit") await codeServerPage.page.click(".submit")
await page.waitForLoadState("networkidle") await codeServerPage.page.waitForLoadState("networkidle")
// We do this because occassionally code-server doesn't load on Firefox // We do this because occassionally code-server doesn't load on Firefox
// but loads if you reload once or twice // but loads if you reload once or twice
await codeServer.reloadUntilEditorIsReady() await codeServerPage.reloadUntilEditorIsReady()
// Make sure the editor actually loaded // 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 // Skip entering password
// Click the submit button and login // Click the submit button and login
await page.click(".submit") await codeServerPage.page.click(".submit")
await page.waitForLoadState("networkidle") await codeServerPage.page.waitForLoadState("networkidle")
expect(await page.isVisible("text=Missing password")) 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 // Type in password
await page.fill(".password", "password123") await codeServerPage.page.fill(".password", "password123")
// Click the submit button and login // Click the submit button and login
await page.click(".submit") await codeServerPage.page.click(".submit")
await page.waitForLoadState("networkidle") await codeServerPage.page.waitForLoadState("networkidle")
expect(await page.isVisible("text=Incorrect password")) 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 // Type in password
await page.fill(".password", "password123") await codeServerPage.page.fill(".password", "password123")
// Click the submit button and login // Click the submit button and login
// The current RateLimiter allows 2 logins per minute plus // The current RateLimiter allows 2 logins per minute plus
// 12 logins per hour for a total of 14 // 12 logins per hour for a total of 14
// See: src/node/routes/login.ts // See: src/node/routes/login.ts
for (let i = 1; i <= 14; i++) { for (let i = 1; i <= 14; i++) {
await page.click(".submit") await codeServerPage.page.click(".submit")
await page.waitForLoadState("networkidle") await codeServerPage.page.waitForLoadState("networkidle")
// We double-check that the correct error message shows // We double-check that the correct error message shows
// which should be for incorrect password // 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: // The 15th should fail for a different reason:
// login rate // login rate
await page.click(".submit") await codeServerPage.page.click(".submit")
await page.waitForLoadState("networkidle") await codeServerPage.page.waitForLoadState("networkidle")
expect(await page.isVisible("text=Login rate limited!")) expect(await codeServerPage.page.isVisible("text=Login rate limited!"))
}) })
}) })

View File

@ -1,53 +1,44 @@
import { test, expect } from "@playwright/test"
import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants" import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants"
import { CodeServer } from "./models/CodeServer" import { test, expect } from "./baseFixture"
test.describe("logout", () => { test.describe("logout", () => {
// Reset the browser so no cookies are persisted // Reset the browser so no cookies are persisted
// by emptying the storageState // by emptying the storageState
const options = { test.use({
contextOptions: {
storageState: {}, storageState: {},
},
}
let codeServer: CodeServer
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.navigate()
}) })
test("should be able login and logout", options, async ({ page }) => { test("should be able login and logout", async ({ codeServerPage }) => {
// Type in password // Type in password
await page.fill(".password", PASSWORD) await codeServerPage.page.fill(".password", PASSWORD)
// Click the submit button and login // Click the submit button and login
await page.click(".submit") await codeServerPage.page.click(".submit")
await page.waitForLoadState("networkidle") await codeServerPage.page.waitForLoadState("networkidle")
// We do this because occassionally code-server doesn't load on Firefox // We do this because occassionally code-server doesn't load on Firefox
// but loads if you reload once or twice // but loads if you reload once or twice
await codeServer.reloadUntilEditorIsReady() await codeServerPage.reloadUntilEditorIsReady()
// Make sure the editor actually loaded // Make sure the editor actually loaded
expect(await codeServer.isEditorVisible()).toBe(true) expect(await codeServerPage.isEditorVisible()).toBe(true)
// Click the Application menu // Click the Application menu
await page.click("[aria-label='Application Menu']") await codeServerPage.page.click("[aria-label='Application Menu']")
// See the Log out button // See the Log out button
const logoutButton = "a.action-menu-item span[aria-label='Log out']" const logoutButton = "a.action-menu-item span[aria-label='Log out']"
expect(await page.isVisible(logoutButton)).toBe(true) expect(await codeServerPage.page.isVisible(logoutButton)).toBe(true)
await page.hover(logoutButton) await codeServerPage.page.hover(logoutButton)
// TODO(@jsjoeio) // TODO(@jsjoeio)
// Look into how we're attaching the handlers for the logout feature // Look into how we're attaching the handlers for the logout feature
// We need to see how it's done upstream and add logging to the // We need to see how it's done upstream and add logging to the
// handlers themselves. // handlers themselves.
// They may be attached too slowly, hence why we need this timeout // They may be attached too slowly, hence why we need this timeout
await page.waitForTimeout(2000) await codeServerPage.page.waitForTimeout(2000)
// Recommended by Playwright for async navigation // Recommended by Playwright for async navigation
// https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151 // https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151
await Promise.all([page.waitForNavigation(), page.click(logoutButton)]) await Promise.all([codeServerPage.page.waitForNavigation(), codeServerPage.page.click(logoutButton)])
const currentUrl = page.url() const currentUrl = codeServerPage.page.url()
expect(currentUrl).toBe(`${CODE_SERVER_ADDRESS}/login`) expect(currentUrl).toBe(`${CODE_SERVER_ADDRESS}/login`)
}) })
}) })

View File

@ -41,7 +41,7 @@ export class CodeServer {
// Give it an extra second just in case it's feeling extra slow // Give it an extra second just in case it's feeling extra slow
await this.page.waitForTimeout(1000) await this.page.waitForTimeout(1000)
reloadCount += 1 reloadCount += 1
if ((await this.isEditorVisible()) && (await this.isConnected)) { if ((await this.isEditorVisible()) && (await this.isConnected())) {
console.log(` Editor became ready after ${reloadCount} reloads`) console.log(` Editor became ready after ${reloadCount} reloads`)
break break
} }
@ -54,8 +54,7 @@ export class CodeServer {
*/ */
async isEditorVisible() { async isEditorVisible() {
// Make sure the editor actually loaded // Make sure the editor actually loaded
// If it's not visible after 5 seconds, something is wrong await this.page.waitForSelector(this.editorSelector)
await this.page.waitForLoadState("networkidle")
return await this.page.isVisible(this.editorSelector) return await this.page.isVisible(this.editorSelector)
} }

View File

@ -1,46 +1,28 @@
import { test, expect } from "@playwright/test" import { storageState } from "../utils/constants"
import { STORAGE } from "../utils/constants" import { test, expect } from "./baseFixture"
import { CodeServer } from "./models/CodeServer"
test.describe("Open Help > About", () => { test.describe("Open Help > About", () => {
// Create a new context with the saved storage state test.use({
// 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, storageState,
}
}
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.setup()
}) })
test( test("should see a 'Help' then 'About' button in the Application Menu that opens a dialog", async ({
"should see a 'Help' then 'About' button in the Application Menu that opens a dialog", codeServerPage,
options, }) => {
async ({ page }) => {
// Open using the manu // Open using the manu
// Click [aria-label="Application Menu"] div[role="none"] // Click [aria-label="Application Menu"] div[role="none"]
await page.click('[aria-label="Application Menu"] div[role="none"]') await codeServerPage.page.click('[aria-label="Application Menu"] div[role="none"]')
// Click the Help button // Click the Help button
await page.hover("text=Help") await codeServerPage.page.hover("text=Help")
await page.click("text=Help") await codeServerPage.page.click("text=Help")
// Click the About button // Click the About button
await page.hover("text=About") await codeServerPage.page.hover("text=About")
await page.click("text=About") await codeServerPage.page.click("text=About")
// Click div[role="dialog"] >> text=code-server // Click div[role="dialog"] >> text=code-server
const element = await page.waitForSelector('div[role="dialog"] >> text=code-server') const element = await codeServerPage.page.waitForSelector('div[role="dialog"] >> text=code-server')
expect(element).not.toBeNull() expect(element).not.toBeNull()
}, })
)
}) })

View File

@ -1,59 +1,46 @@
import { expect, test } from "@playwright/test"
import * as cp from "child_process" import * as cp from "child_process"
import * as fs from "fs" import * as fs from "fs"
import * as path from "path" import * as path from "path"
import util from "util" import util from "util"
import { STORAGE } from "../utils/constants" import { storageState } from "../utils/constants"
import { tmpdir } from "../utils/helpers" import { tmpdir } from "../utils/helpers"
import { CodeServer } from "./models/CodeServer" import { expect, test } from "./baseFixture"
test.describe("Integrated Terminal", () => { test.describe("Integrated Terminal", () => {
// Create a new context with the saved storage state // Create a new context with the saved storage state
// so we don't have to logged in // so we don't have to logged in
const options: any = {}
const testFileName = "pipe" const testFileName = "pipe"
const testString = "new string test from e2e test" const testString = "new string test from e2e test"
let codeServer: CodeServer
let tmpFolderPath = "" let tmpFolderPath = ""
let tmpFile = "" let tmpFile = ""
// TODO@jsjoeio test.use({
// Fix this once https://github.com/microsoft/playwright-test/issues/240
// is fixed
if (STORAGE) {
const storageState = JSON.parse(STORAGE) || {}
options.contextOptions = {
storageState, storageState,
} })
}
test.beforeAll(async () => { test.beforeAll(async () => {
tmpFolderPath = await tmpdir("integrated-terminal") tmpFolderPath = await tmpdir("integrated-terminal")
tmpFile = path.join(tmpFolderPath, testFileName) tmpFile = path.join(tmpFolderPath, testFileName)
}) })
test.beforeEach(async ({ page }) => {
codeServer = new CodeServer(page)
await codeServer.setup()
})
test.afterAll(async () => { test.afterAll(async () => {
// Ensure directory was removed // Ensure directory was removed
await fs.promises.rmdir(tmpFolderPath, { recursive: true }) await fs.promises.rmdir(tmpFolderPath, { recursive: true })
}) })
test("should echo a string to a file", options, async ({ page }) => { test("should echo a string to a file", async ({ codeServerPage }) => {
const command = `mkfifo '${tmpFile}' && cat '${tmpFile}'` const command = `mkfifo '${tmpFile}' && cat '${tmpFile}'`
const exec = util.promisify(cp.exec) const exec = util.promisify(cp.exec)
const output = exec(command, { encoding: "utf8" }) const output = exec(command, { encoding: "utf8" })
// Open terminal and type in value // Open terminal and type in value
await codeServer.focusTerminal() await codeServerPage.focusTerminal()
await page.waitForLoadState("load") await codeServerPage.page.waitForLoadState("load")
await page.keyboard.type(`echo ${testString} > ${tmpFile}`) await codeServerPage.page.keyboard.type(`echo ${testString} > ${tmpFile}`)
await page.keyboard.press("Enter") await codeServerPage.page.keyboard.press("Enter")
// It may take a second to process // It may take a second to process
await page.waitForTimeout(1000) await codeServerPage.page.waitForTimeout(1000)
const { stdout } = await output const { stdout } = await output
expect(stdout).toMatch(testString) expect(stdout).toMatch(testString)

View File

@ -2,7 +2,7 @@
"license": "MIT", "license": "MIT",
"#": "We must put jest in a sub-directory otherwise VS Code somehow picks up the types and generates conflicts with mocha.", "#": "We must put jest in a sub-directory otherwise VS Code somehow picks up the types and generates conflicts with mocha.",
"devDependencies": { "devDependencies": {
"@playwright/test": "^0.1101.0-alpha2", "@playwright/test": "^1.12.1",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/jsdom": "^16.2.6", "@types/jsdom": "^16.2.6",
"@types/node-fetch": "^2.5.8", "@types/node-fetch": "^2.5.8",
@ -11,11 +11,8 @@
"jest": "^26.6.3", "jest": "^26.6.3",
"jsdom": "^16.4.0", "jsdom": "^16.4.0",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"playwright": "^1.11.0-next-alpha-apr-13-2021", "playwright": "^1.12.1",
"supertest": "^6.1.1", "supertest": "^6.1.1",
"ts-jest": "^26.4.4" "ts-jest": "^26.4.4"
},
"resolutions": {
"@playwright/test/playwright": "^1.11.0-next-alpha-apr-13-2021"
} }
} }

43
test/playwright.config.ts Normal file
View File

@ -0,0 +1,43 @@
import { PlaywrightTestConfig } from "@playwright/test"
import path from "path"
// Run tests in three browsers.
const config: PlaywrightTestConfig = {
testDir: path.join(__dirname, "e2e"), // Search for tests in this directory.
timeout: 60000, // Each test is given 60 seconds.
retries: 3, // Retry failing tests 2 times
workers: 1,
globalSetup: require.resolve("./utils/globalSetup.ts"),
reporter: "list",
// Put any shared options on the top level.
use: {
headless: true, // Run tests in headless browsers.
video: "on",
},
projects: [
{
name: "Chromium",
use: { browserName: "chromium" },
},
{
name: "Firefox",
use: { browserName: "firefox" },
},
{
name: "WebKit",
use: { browserName: "webkit" },
},
],
}
if (process.env.CI) {
// In CI, retry failing tests 2 times
// in the event of flakiness
config.retries = 2
}
export default config

View File

@ -1,3 +1,3 @@
export const CODE_SERVER_ADDRESS = process.env.CODE_SERVER_ADDRESS || "http://localhost:8080" export const CODE_SERVER_ADDRESS = process.env.CODE_SERVER_ADDRESS || "http://localhost:8080"
export const PASSWORD = process.env.PASSWORD || "e45432jklfdsab" export const PASSWORD = process.env.PASSWORD || "e45432jklfdsab"
export const STORAGE = process.env.STORAGE || "" export const storageState = JSON.parse(process.env.STORAGE || "{}")

View File

@ -6,10 +6,14 @@ import { hash } from "../../src/node/util"
import { PASSWORD } from "./constants" import { PASSWORD } from "./constants"
import * as wtfnode from "./wtfnode" import * as wtfnode from "./wtfnode"
export default async function () {
console.log("\n🚨 Running Global Setup for Playwright End-to-End Tests")
console.log(" Please hang tight...")
const cookieToStore = { const cookieToStore = {
sameSite: "Lax" as const, sameSite: "Lax" as const,
name: "key", name: "key",
value: hash(PASSWORD), value: await hash(PASSWORD),
domain: "localhost", domain: "localhost",
path: "/", path: "/",
expires: -1, expires: -1,
@ -17,9 +21,6 @@ const cookieToStore = {
secure: false, secure: false,
} }
module.exports = async () => {
console.log("\n🚨 Running Global Setup for Jest End-to-End Tests")
console.log(" Please hang tight...")
const browser = await chromium.launch() const browser = await chromium.launch()
const page = await browser.newPage() const page = await browser.newPage()
const storage = await page.context().storageState() const storage = await page.context().storageState()
@ -31,10 +32,9 @@ module.exports = async () => {
storage.cookies = [cookieToStore] storage.cookies = [cookieToStore]
// Save storage state and store as an env variable // Save storage state and store as an env variable
// More info: https://playwright.dev/docs/auth?_highlight=authe#reuse-authentication-state // More info: https://playwright.dev/docs/auth/#reuse-authentication-state
process.env.STORAGE = JSON.stringify(storage) process.env.STORAGE = JSON.stringify(storage)
await page.close()
await browser.close() await browser.close()
console.log("✅ Global Setup for Jest End-to-End Tests is now complete.") console.log("✅ Global Setup for Playwright End-to-End Tests is now complete.")
} }

File diff suppressed because it is too large Load Diff