Skip to content

Commit f2fa770

Browse files
committed
Centralize credential handling
My thinking is that this may reduce the cognitive overhead for developers writing new test suites. This also allows us to perform different setup steps (like ensuring the editor is visible when authenticated).
1 parent da4de43 commit f2fa770

11 files changed

+54
-66
lines changed

test/e2e/baseFixture.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { CodeServer, CodeServerPage } from "./models/CodeServer"
66
* Wraps `test.describe` to create and manage an instance of code-server. If you
77
* don't use this you will need to create your own code-server instance and pass
88
* it to `test.use`.
9+
*
10+
* If `includeCredentials` is `true` page requests will be authenticated.
911
*/
10-
export const describe = (name: string, fn: (codeServer: CodeServer) => void) => {
12+
export const describe = (name: string, includeCredentials: boolean, fn: (codeServer: CodeServer) => void) => {
1113
test.describe(name, () => {
1214
// This will spawn on demand so nothing is necessary on before.
1315
const codeServer = new CodeServer(name)
@@ -18,14 +20,30 @@ export const describe = (name: string, fn: (codeServer: CodeServer) => void) =>
1820
await codeServer.close()
1921
})
2022

21-
// This makes `codeServer` available to the extend call below.
22-
test.use({ codeServer })
23+
const storageState = JSON.parse(process.env.STORAGE || "{}")
24+
25+
// Sanity check to ensure the cookie is set.
26+
const cookies = storageState?.cookies
27+
if (includeCredentials && (!cookies || cookies.length !== 1 || !!cookies[0].key)) {
28+
logger.error("no cookies", field("storage", JSON.stringify(cookies)))
29+
throw new Error("no credentials to include")
30+
}
31+
32+
test.use({
33+
// Makes `codeServer` and `authenticated` available to the extend call
34+
// below.
35+
codeServer,
36+
authenticated: includeCredentials,
37+
// This provides a cookie that authenticates with code-server.
38+
storageState: includeCredentials ? storageState : {},
39+
})
2340

2441
fn(codeServer)
2542
})
2643
}
2744

2845
interface TestFixtures {
46+
authenticated: boolean
2947
codeServer: CodeServer
3048
codeServerPage: CodeServerPage
3149
}
@@ -35,10 +53,11 @@ interface TestFixtures {
3553
* ready.
3654
*/
3755
export const test = base.extend<TestFixtures>({
56+
authenticated: false,
3857
codeServer: undefined, // No default; should be provided through `test.use`.
39-
codeServerPage: async ({ codeServer, page }, use) => {
58+
codeServerPage: async ({ authenticated, codeServer, page }, use) => {
4059
const codeServerPage = new CodeServerPage(codeServer, page)
41-
await codeServerPage.navigate()
60+
await codeServerPage.setup(authenticated)
4261
await use(codeServerPage)
4362
},
4463
})

test/e2e/browser.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, test, expect } from "./baseFixture"
22

33
// This is a "gut-check" test to make sure playwright is working as expected
4-
describe("browser", () => {
4+
describe("browser", true, () => {
55
test("browser should display correct userAgent", async ({ codeServerPage, browserName }) => {
66
const displayNames = {
77
chromium: "Chrome",

test/e2e/codeServer.test.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
import { storageState } from "../utils/constants"
21
import { describe, test, expect } from "./baseFixture"
32

4-
describe("CodeServer", () => {
5-
test.use({
6-
storageState,
7-
})
8-
3+
describe("CodeServer", true, () => {
94
test("should navigate to home page", async ({ codeServerPage }) => {
105
// We navigate codeServer before each test
116
// and we start the test with a storage state

test/e2e/globalSetup.test.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import { storageState } from "../utils/constants"
21
import { describe, test, expect } from "./baseFixture"
32

43
// This test is to make sure the globalSetup works as expected
54
// meaning globalSetup ran and stored the storageState
6-
describe("globalSetup", () => {
7-
test.use({
8-
storageState,
9-
})
10-
5+
describe("globalSetup", true, () => {
116
test("should keep us logged in using the storageState", async ({ codeServerPage }) => {
127
// Make sure the editor actually loaded
138
expect(await codeServerPage.isEditorVisible()).toBe(true)

test/e2e/login.test.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import { PASSWORD } from "../utils/constants"
22
import { describe, test, expect } from "./baseFixture"
33

4-
describe("login", () => {
5-
// Reset the browser so no cookies are persisted
6-
// by emptying the storageState
7-
test.use({
8-
storageState: {},
9-
})
10-
4+
describe("login", false, () => {
115
test("should see the login page", async ({ codeServerPage }) => {
126
// It should send us to the login page
137
expect(await codeServerPage.page.title()).toBe("code-server login")

test/e2e/logout.test.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import { PASSWORD } from "../utils/constants"
22
import { describe, test, expect } from "./baseFixture"
33

4-
describe("logout", () => {
5-
// Reset the browser so no cookies are persisted
6-
// by emptying the storageState
7-
test.use({
8-
storageState: {},
9-
})
10-
4+
describe("logout", false, () => {
115
test("should be able login and logout", async ({ codeServerPage }) => {
126
// Type in password
137
await codeServerPage.page.fill(".password", PASSWORD)

test/e2e/models/CodeServer.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,12 @@ export class CodeServerPage {
250250
*
251251
* It is recommended to run setup before using this model in any tests.
252252
*/
253-
async setup() {
253+
async setup(authenticated: boolean) {
254254
await this.navigate()
255-
await this.reloadUntilEditorIsReady()
255+
// If we aren't authenticated we'll see a login page so we can't wait until
256+
// the editor is ready.
257+
if (authenticated) {
258+
await this.reloadUntilEditorIsReady()
259+
}
256260
}
257261
}

test/e2e/openHelpAbout.test.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
import { storageState } from "../utils/constants"
21
import { describe, test, expect } from "./baseFixture"
32

4-
describe("Open Help > About", () => {
5-
test.use({
6-
storageState,
7-
})
8-
3+
describe("Open Help > About", true, () => {
94
test("should see a 'Help' then 'About' button in the Application Menu that opens a dialog", async ({
105
codeServerPage,
116
}) => {

test/e2e/terminal.test.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,17 @@ import * as cp from "child_process"
22
import * as fs from "fs"
33
import * as path from "path"
44
import util from "util"
5-
import { storageState } from "../utils/constants"
65
import { tmpdir } from "../utils/helpers"
76
import { describe, expect, test } from "./baseFixture"
87

9-
describe("Integrated Terminal", () => {
8+
describe("Integrated Terminal", true, () => {
109
// Create a new context with the saved storage state
1110
// so we don't have to logged in
1211
const testFileName = "pipe"
1312
const testString = "new string test from e2e test"
1413
let tmpFolderPath = ""
1514
let tmpFile = ""
1615

17-
test.use({
18-
storageState,
19-
})
20-
2116
test.beforeAll(async () => {
2217
tmpFolderPath = await tmpdir("integrated-terminal")
2318
tmpFile = path.join(tmpFolderPath, testFileName)

test/utils/constants.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export const PASSWORD = "e45432jklfdsab"
2-
export const storageState = JSON.parse(process.env.STORAGE || "{}")
32
export const workspaceDir = "workspaces"

test/utils/globalSetup.ts

+17-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { chromium } from "playwright"
1+
import { Cookie } from "playwright"
22
import { hash } from "../../src/node/util"
33
import { PASSWORD, workspaceDir } from "./constants"
44
import { clean } from "./helpers"
@@ -15,31 +15,29 @@ export default async function () {
1515
// Cleanup workspaces from previous tests.
1616
await clean(workspaceDir)
1717

18-
const cookieToStore = {
19-
sameSite: "Lax" as const,
20-
name: "key",
21-
value: await hash(PASSWORD),
22-
domain: "localhost",
23-
path: "/",
24-
expires: -1,
25-
httpOnly: false,
26-
secure: false,
27-
}
28-
29-
const browser = await chromium.launch()
30-
const page = await browser.newPage()
31-
const storage = await page.context().storageState()
32-
3318
if (process.env.WTF_NODE) {
3419
wtfnode.setup()
3520
}
3621

37-
storage.cookies = [cookieToStore]
22+
// TODO: Replace this with a call to code-server to get the cookie. To avoid
23+
// too much overhead we can do an http POST request and avoid spawning a
24+
// browser for it.
25+
const cookies: Cookie[] = [
26+
{
27+
domain: "localhost",
28+
expires: -1,
29+
httpOnly: false,
30+
name: "key",
31+
path: "/",
32+
sameSite: "Lax",
33+
secure: false,
34+
value: await hash(PASSWORD),
35+
},
36+
]
3837

3938
// Save storage state and store as an env variable
4039
// More info: https://playwright.dev/docs/auth/#reuse-authentication-state
41-
process.env.STORAGE = JSON.stringify(storage)
42-
await browser.close()
40+
process.env.STORAGE = JSON.stringify({ cookies })
4341

4442
console.log("✅ Global Setup for Playwright End-to-End Tests is now complete.")
4543
}

0 commit comments

Comments
 (0)