diff --git a/test/e2e/baseFixture.ts b/test/e2e/baseFixture.ts index b4105c227..e67d09b8d 100644 --- a/test/e2e/baseFixture.ts +++ b/test/e2e/baseFixture.ts @@ -6,8 +6,10 @@ import { CodeServer, CodeServerPage } from "./models/CodeServer" * Wraps `test.describe` to create and manage an instance of code-server. If you * don't use this you will need to create your own code-server instance and pass * it to `test.use`. + * + * If `includeCredentials` is `true` page requests will be authenticated. */ -export const describe = (name: string, fn: (codeServer: CodeServer) => void) => { +export const describe = (name: string, includeCredentials: boolean, fn: (codeServer: CodeServer) => void) => { test.describe(name, () => { // This will spawn on demand so nothing is necessary on before. const codeServer = new CodeServer(name) @@ -18,14 +20,30 @@ export const describe = (name: string, fn: (codeServer: CodeServer) => void) => await codeServer.close() }) - // This makes `codeServer` available to the extend call below. - test.use({ codeServer }) + const storageState = JSON.parse(process.env.STORAGE || "{}") + + // Sanity check to ensure the cookie is set. + const cookies = storageState?.cookies + if (includeCredentials && (!cookies || cookies.length !== 1 || !!cookies[0].key)) { + logger.error("no cookies", field("storage", JSON.stringify(cookies))) + throw new Error("no credentials to include") + } + + test.use({ + // Makes `codeServer` and `authenticated` available to the extend call + // below. + codeServer, + authenticated: includeCredentials, + // This provides a cookie that authenticates with code-server. + storageState: includeCredentials ? storageState : {}, + }) fn(codeServer) }) } interface TestFixtures { + authenticated: boolean codeServer: CodeServer codeServerPage: CodeServerPage } @@ -35,10 +53,11 @@ interface TestFixtures { * ready. */ export const test = base.extend({ + authenticated: false, codeServer: undefined, // No default; should be provided through `test.use`. - codeServerPage: async ({ codeServer, page }, use) => { + codeServerPage: async ({ authenticated, codeServer, page }, use) => { const codeServerPage = new CodeServerPage(codeServer, page) - await codeServerPage.navigate() + await codeServerPage.setup(authenticated) await use(codeServerPage) }, }) diff --git a/test/e2e/browser.test.ts b/test/e2e/browser.test.ts index 078b4e309..3c3b2411c 100644 --- a/test/e2e/browser.test.ts +++ b/test/e2e/browser.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from "./baseFixture" // This is a "gut-check" test to make sure playwright is working as expected -describe("browser", () => { +describe("browser", true, () => { test("browser should display correct userAgent", async ({ codeServerPage, browserName }) => { const displayNames = { chromium: "Chrome", diff --git a/test/e2e/codeServer.test.ts b/test/e2e/codeServer.test.ts index a40e30d89..cfac6f74c 100644 --- a/test/e2e/codeServer.test.ts +++ b/test/e2e/codeServer.test.ts @@ -1,11 +1,6 @@ -import { storageState } from "../utils/constants" import { describe, test, expect } from "./baseFixture" -describe("CodeServer", () => { - test.use({ - storageState, - }) - +describe("CodeServer", true, () => { test("should navigate to home page", async ({ codeServerPage }) => { // We navigate codeServer before each test // and we start the test with a storage state diff --git a/test/e2e/globalSetup.test.ts b/test/e2e/globalSetup.test.ts index ed3506ea9..8b4589b15 100644 --- a/test/e2e/globalSetup.test.ts +++ b/test/e2e/globalSetup.test.ts @@ -1,13 +1,8 @@ -import { storageState } from "../utils/constants" import { describe, test, expect } from "./baseFixture" // This test is to make sure the globalSetup works as expected // meaning globalSetup ran and stored the storageState -describe("globalSetup", () => { - test.use({ - storageState, - }) - +describe("globalSetup", true, () => { test("should keep us logged in using the storageState", async ({ codeServerPage }) => { // Make sure the editor actually loaded expect(await codeServerPage.isEditorVisible()).toBe(true) diff --git a/test/e2e/login.test.ts b/test/e2e/login.test.ts index b2289740a..bc9d5e8e9 100644 --- a/test/e2e/login.test.ts +++ b/test/e2e/login.test.ts @@ -1,13 +1,7 @@ import { PASSWORD } from "../utils/constants" import { describe, test, expect } from "./baseFixture" -describe("login", () => { - // Reset the browser so no cookies are persisted - // by emptying the storageState - test.use({ - storageState: {}, - }) - +describe("login", false, () => { test("should see the login page", async ({ codeServerPage }) => { // It should send us to the login page expect(await codeServerPage.page.title()).toBe("code-server login") diff --git a/test/e2e/logout.test.ts b/test/e2e/logout.test.ts index ec480c4f1..ac932fa47 100644 --- a/test/e2e/logout.test.ts +++ b/test/e2e/logout.test.ts @@ -1,13 +1,7 @@ import { PASSWORD } from "../utils/constants" import { describe, test, expect } from "./baseFixture" -describe("logout", () => { - // Reset the browser so no cookies are persisted - // by emptying the storageState - test.use({ - storageState: {}, - }) - +describe("logout", false, () => { test("should be able login and logout", async ({ codeServerPage }) => { // Type in password await codeServerPage.page.fill(".password", PASSWORD) diff --git a/test/e2e/models/CodeServer.ts b/test/e2e/models/CodeServer.ts index b3e2bdafa..42600df3d 100644 --- a/test/e2e/models/CodeServer.ts +++ b/test/e2e/models/CodeServer.ts @@ -250,8 +250,12 @@ export class CodeServerPage { * * It is recommended to run setup before using this model in any tests. */ - async setup() { + async setup(authenticated: boolean) { await this.navigate() - await this.reloadUntilEditorIsReady() + // If we aren't authenticated we'll see a login page so we can't wait until + // the editor is ready. + if (authenticated) { + await this.reloadUntilEditorIsReady() + } } } diff --git a/test/e2e/openHelpAbout.test.ts b/test/e2e/openHelpAbout.test.ts index 1cb030540..47daaaf14 100644 --- a/test/e2e/openHelpAbout.test.ts +++ b/test/e2e/openHelpAbout.test.ts @@ -1,11 +1,6 @@ -import { storageState } from "../utils/constants" import { describe, test, expect } from "./baseFixture" -describe("Open Help > About", () => { - test.use({ - storageState, - }) - +describe("Open Help > About", true, () => { test("should see a 'Help' then 'About' button in the Application Menu that opens a dialog", async ({ codeServerPage, }) => { diff --git a/test/e2e/terminal.test.ts b/test/e2e/terminal.test.ts index b2b284c74..836583a8d 100644 --- a/test/e2e/terminal.test.ts +++ b/test/e2e/terminal.test.ts @@ -2,11 +2,10 @@ import * as cp from "child_process" import * as fs from "fs" import * as path from "path" import util from "util" -import { storageState } from "../utils/constants" import { tmpdir } from "../utils/helpers" import { describe, expect, test } from "./baseFixture" -describe("Integrated Terminal", () => { +describe("Integrated Terminal", true, () => { // Create a new context with the saved storage state // so we don't have to logged in const testFileName = "pipe" @@ -14,10 +13,6 @@ describe("Integrated Terminal", () => { let tmpFolderPath = "" let tmpFile = "" - test.use({ - storageState, - }) - test.beforeAll(async () => { tmpFolderPath = await tmpdir("integrated-terminal") tmpFile = path.join(tmpFolderPath, testFileName) diff --git a/test/utils/constants.ts b/test/utils/constants.ts index 08acb9788..9b5c2b6e8 100644 --- a/test/utils/constants.ts +++ b/test/utils/constants.ts @@ -1,3 +1,2 @@ export const PASSWORD = "e45432jklfdsab" -export const storageState = JSON.parse(process.env.STORAGE || "{}") export const workspaceDir = "workspaces" diff --git a/test/utils/globalSetup.ts b/test/utils/globalSetup.ts index 8d94c7438..eace7f9b2 100644 --- a/test/utils/globalSetup.ts +++ b/test/utils/globalSetup.ts @@ -1,4 +1,4 @@ -import { chromium } from "playwright" +import { Cookie } from "playwright" import { hash } from "../../src/node/util" import { PASSWORD, workspaceDir } from "./constants" import { clean } from "./helpers" @@ -15,31 +15,29 @@ export default async function () { // Cleanup workspaces from previous tests. await clean(workspaceDir) - const cookieToStore = { - sameSite: "Lax" as const, - name: "key", - value: await hash(PASSWORD), - domain: "localhost", - path: "/", - expires: -1, - httpOnly: false, - secure: false, - } - - const browser = await chromium.launch() - const page = await browser.newPage() - const storage = await page.context().storageState() - if (process.env.WTF_NODE) { wtfnode.setup() } - storage.cookies = [cookieToStore] + // TODO: Replace this with a call to code-server to get the cookie. To avoid + // too much overhead we can do an http POST request and avoid spawning a + // browser for it. + const cookies: Cookie[] = [ + { + domain: "localhost", + expires: -1, + httpOnly: false, + name: "key", + path: "/", + sameSite: "Lax", + secure: false, + value: await hash(PASSWORD), + }, + ] // Save storage state and store as an env variable // More info: https://playwright.dev/docs/auth/#reuse-authentication-state - process.env.STORAGE = JSON.stringify(storage) - await browser.close() + process.env.STORAGE = JSON.stringify({ cookies }) console.log("✅ Global Setup for Playwright End-to-End Tests is now complete.") }