Skip to content

Commit f178f04

Browse files
jsjoeiocode-asher
andauthored
feat(e2e): add support running behind proxy (#5348)
* docs: update maintaining * chore(e2e): add maxFailures to playwright * fix(ci): skip submodule in e2e job We don't need the submodules for the e2e job. This will speed up the checkout step. * feat(ci): add test-e2e-proxy job This adds a new job to CI to run our tests behind Caddy and simulate code-server running against a reverse-proxy. * refactor: make e2e work with reverse proxy This refactors the e2e test in a couple ways: - remove setting cookie in localStorage (instead we pass --auth none) - refactor address() method to account for reverse proxy logic * Update test/e2e/models/CodeServer.ts * Update test/playwright.config.ts * Update test/utils/constants.ts Co-authored-by: Asher <ash@coder.com> * Update test/utils/helpers.ts Co-authored-by: Asher <ash@coder.com> Co-authored-by: Asher <ash@coder.com>
1 parent efb5bae commit f178f04

18 files changed

+192
-85
lines changed

.github/workflows/ci.yaml

+88-2
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,6 @@ jobs:
461461
uses: actions/checkout@v3
462462
with:
463463
fetch-depth: 0
464-
submodules: true
465464

466465
- name: Install Node.js v16
467466
uses: actions/setup-node@v3
@@ -491,7 +490,7 @@ jobs:
491490
492491
- name: Install dependencies
493492
if: steps.cache-yarn.outputs.cache-hit != 'true'
494-
run: yarn --frozen-lockfile
493+
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
495494

496495
- name: Install Playwright OS dependencies
497496
run: |
@@ -511,6 +510,93 @@ jobs:
511510
- name: Remove release packages and test artifacts
512511
run: rm -rf ./release-packages ./test/test-results
513512

513+
test-e2e-proxy:
514+
name: End-to-end tests behind proxy
515+
needs: package-linux-amd64
516+
runs-on: ubuntu-latest
517+
timeout-minutes: 25
518+
env:
519+
# Since we build code-server we might as well run tests from the release
520+
# since VS Code will load faster due to the bundling.
521+
CODE_SERVER_TEST_ENTRY: "./release-packages/code-server-linux-amd64"
522+
steps:
523+
- name: Checkout repo
524+
uses: actions/checkout@v3
525+
with:
526+
fetch-depth: 0
527+
528+
- name: Install Node.js v16
529+
uses: actions/setup-node@v3
530+
with:
531+
node-version: "16"
532+
533+
- name: Fetch dependencies from cache
534+
id: cache-yarn
535+
uses: actions/cache@v3
536+
with:
537+
path: "**/node_modules"
538+
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
539+
restore-keys: |
540+
yarn-build-
541+
542+
- name: Download release packages
543+
uses: actions/download-artifact@v3
544+
with:
545+
name: release-packages
546+
path: ./release-packages
547+
548+
- name: Untar code-server release
549+
run: |
550+
cd release-packages
551+
tar -xzf code-server*-linux-amd64.tar.gz
552+
mv code-server*-linux-amd64 code-server-linux-amd64
553+
554+
- name: Install dependencies
555+
if: steps.cache-yarn.outputs.cache-hit != 'true'
556+
run: SKIP_SUBMODULE_DEPS=1 yarn --frozen-lockfile
557+
558+
- name: Install Playwright OS dependencies
559+
run: |
560+
./test/node_modules/.bin/playwright install-deps
561+
./test/node_modules/.bin/playwright install
562+
563+
- name: Cache Caddy
564+
uses: actions/cache@v2
565+
id: caddy-cache
566+
with:
567+
path: |
568+
~/.cache/caddy
569+
key: cache-caddy-2.5.2
570+
571+
- name: Install Caddy
572+
env:
573+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
574+
if: steps.caddy-cache.outputs.cache-hit != 'true'
575+
run: |
576+
gh release download v2.5.2 --repo caddyserver/caddy --pattern "caddy_2.5.2_linux_amd64.tar.gz"
577+
mkdir -p ~/.cache/caddy
578+
tar -xzf caddy_2.5.2_linux_amd64.tar.gz --directory ~/.cache/caddy
579+
580+
- name: Start Caddy
581+
run: sudo ~/.cache/caddy/caddy start --config ./ci/Caddyfile
582+
583+
- name: Run end-to-end tests
584+
run: yarn test:e2e:proxy
585+
586+
- name: Stop Caddy
587+
if: always()
588+
run: sudo ~/.cache/caddy/caddy stop --config ./ci/Caddyfile
589+
590+
- name: Upload test artifacts
591+
if: always()
592+
uses: actions/upload-artifact@v3
593+
with:
594+
name: failed-test-videos-proxy
595+
path: ./test/test-results
596+
597+
- name: Remove release packages and test artifacts
598+
run: rm -rf ./release-packages ./test/test-results
599+
514600
trivy-scan-repo:
515601
permissions:
516602
contents: read # for actions/checkout to fetch code

ci/Caddyfile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
admin localhost:4444
3+
}
4+
:8000 {
5+
@portLocalhost path_regexp port ^/([0-9]+)\/ide
6+
handle @portLocalhost {
7+
uri strip_prefix {re.port.1}/ide
8+
reverse_proxy localhost:{re.port.1}
9+
}
10+
11+
handle {
12+
respond "Bad hostname" 400
13+
}
14+
15+
}

docs/MAINTAINING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ If you're the current release manager, follow these steps:
175175
1. Bump chart version in `Chart.yaml`.
176176
1. Summarize the major changes in the release notes and link to the relevant
177177
issues.
178-
1. Change the @ to target the version branch. Example: `v3.9.0 @ Target: v3.9.0`
178+
1. Change the @ to target the version branch. Example: `v3.9.0 @ Target: release/v3.9.0`
179179
1. Wait for the `npm-package`, `release-packages` and `release-images` artifacts
180180
to build.
181181
1. Run `yarn release:github-assets` to download the `release-packages` artifact.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"release:github-assets": "./ci/build/release-github-assets.sh",
1919
"release:prep": "./ci/build/release-prep.sh",
2020
"test:e2e": "VSCODE_IPC_HOOK_CLI= ./ci/dev/test-e2e.sh",
21+
"test:e2e:proxy": "USE_PROXY=1 ./ci/dev/test-e2e.sh",
2122
"test:unit": "./ci/dev/test-unit.sh --forceExit --detectOpenHandles",
2223
"test:integration": "./ci/dev/test-integration.sh",
2324
"test:scripts": "./ci/dev/test-scripts.sh",

test/e2e/baseFixture.ts

+3-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { field, logger } from "@coder/logger"
21
import { test as base } from "@playwright/test"
32
import { CodeServer, CodeServerPage } from "./models/CodeServer"
43

@@ -11,37 +10,24 @@ import { CodeServer, CodeServerPage } from "./models/CodeServer"
1110
*/
1211
export const describe = (
1312
name: string,
14-
includeCredentials: boolean,
1513
codeServerArgs: string[],
1614
codeServerEnv: NodeJS.ProcessEnv,
1715
fn: (codeServer: CodeServer) => void,
1816
) => {
1917
test.describe(name, () => {
2018
// This will spawn on demand so nothing is necessary on before.
21-
const codeServer = new CodeServer(name, codeServerArgs, codeServerEnv)
19+
const codeServer = new CodeServer(name, codeServerArgs, codeServerEnv, undefined)
2220

2321
// Kill code-server after the suite has ended. This may happen even without
2422
// doing it explicitly but it seems prudent to be sure.
2523
test.afterAll(async () => {
2624
await codeServer.close()
2725
})
2826

29-
const storageState = JSON.parse(process.env.STORAGE || "{}")
30-
31-
// Sanity check to ensure the cookie is set.
32-
const cookies = storageState?.cookies
33-
if (includeCredentials && (!cookies || cookies.length !== 1 || !!cookies[0].key)) {
34-
logger.error("no cookies", field("storage", JSON.stringify(cookies)))
35-
throw new Error("no credentials to include")
36-
}
37-
3827
test.use({
3928
// Makes `codeServer` and `authenticated` available to the extend call
4029
// below.
4130
codeServer,
42-
authenticated: includeCredentials,
43-
// This provides a cookie that authenticates with code-server.
44-
storageState: includeCredentials ? storageState : {},
4531
// NOTE@jsjoeio some tests use --cert which uses a self-signed certificate
4632
// without this option, those tests will fail.
4733
ignoreHTTPSErrors: true,
@@ -52,7 +38,6 @@ export const describe = (
5238
}
5339

5440
interface TestFixtures {
55-
authenticated: boolean
5641
codeServer: CodeServer
5742
codeServerPage: CodeServerPage
5843
}
@@ -62,15 +47,14 @@ interface TestFixtures {
6247
* ready.
6348
*/
6449
export const test = base.extend<TestFixtures>({
65-
authenticated: false,
6650
codeServer: undefined, // No default; should be provided through `test.use`.
67-
codeServerPage: async ({ authenticated, codeServer, page }, use) => {
51+
codeServerPage: async ({ codeServer, page }, use) => {
6852
// It's possible code-server might prevent navigation because of unsaved
6953
// changes (seems to happen based on timing even if no changes have been
7054
// made too). In these cases just accept.
7155
page.on("dialog", (d) => d.accept())
7256

73-
const codeServerPage = new CodeServerPage(codeServer, page, authenticated)
57+
const codeServerPage = new CodeServerPage(codeServer, page)
7458
await codeServerPage.navigate()
7559
await use(codeServerPage)
7660
},

test/e2e/codeServer.test.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { promises as fs } from "fs"
33
import * as os from "os"
44
import * as path from "path"
55
import * as util from "util"
6+
import { getMaybeProxiedCodeServer } from "../utils/helpers"
67
import { describe, test, expect } from "./baseFixture"
78
import { CodeServer } from "./models/CodeServer"
89

9-
describe("code-server", true, [], {}, () => {
10+
describe("code-server", [], {}, () => {
1011
// TODO@asher: Generalize this? Could be nice if we were to ever need
1112
// multiple migration tests in other suites.
1213
const instances = new Map<string, CodeServer>()
@@ -48,7 +49,8 @@ describe("code-server", true, [], {}, () => {
4849
const url = codeServerPage.page.url()
4950
// We use match because there may be a / at the end
5051
// so we don't want it to fail if we expect http://localhost:8080 to match http://localhost:8080/
51-
expect(url).toMatch(await codeServerPage.address())
52+
const address = await getMaybeProxiedCodeServer(codeServerPage)
53+
expect(url).toMatch(address)
5254
})
5355

5456
test("should always see the code-server editor", async ({ codeServerPage }) => {
@@ -70,7 +72,9 @@ describe("code-server", true, [], {}, () => {
7072
test("should migrate state to avoid collisions", async ({ codeServerPage }) => {
7173
// This can take a very long time in development because of how long pages
7274
// take to load and we are doing a lot of that here.
73-
test.slow()
75+
if (process.env.VSCODE_DEV === "1") {
76+
test.slow()
77+
}
7478

7579
const dir = await codeServerPage.workspaceDir
7680
const files = [path.join(dir, "foo"), path.join(dir, "bar")]
@@ -90,6 +94,7 @@ describe("code-server", true, [], {}, () => {
9094
// domain and can write to the same database.
9195
const cs = await spawn("4.0.2", dir)
9296
const address = new URL(await cs.address())
97+
9398
await codeServerPage.navigate("/proxy/" + address.port + "/")
9499
await codeServerPage.openFile(files[1])
95100
expect(await codeServerPage.tabIsVisible(files[0])).toBe(false)

test/e2e/downloads.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from "path"
33
import { clean } from "../utils/helpers"
44
import { describe, test, expect } from "./baseFixture"
55

6-
describe("Downloads (enabled)", true, [], {}, async () => {
6+
describe("Downloads (enabled)", [], {}, async () => {
77
const testName = "downloads-enabled"
88
test.beforeAll(async () => {
99
await clean(testName)
@@ -25,7 +25,7 @@ describe("Downloads (enabled)", true, [], {}, async () => {
2525
})
2626
})
2727

28-
describe("Downloads (disabled)", true, ["--disable-file-downloads"], {}, async () => {
28+
describe("Downloads (disabled)", ["--disable-file-downloads"], {}, async () => {
2929
const testName = "downloads-disabled"
3030
test.beforeAll(async () => {
3131
await clean(testName)

test/e2e/extensions.test.ts

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
11
import * as path from "path"
2-
import { describe, test } from "./baseFixture"
2+
import { test as base } from "@playwright/test"
3+
import { describe, test, expect } from "./baseFixture"
4+
import { getMaybeProxiedCodeServer } from "../utils/helpers"
35

46
function runTestExtensionTests() {
57
// This will only work if the test extension is loaded into code-server.
68
test("should have access to VSCODE_PROXY_URI", async ({ codeServerPage }) => {
7-
const address = await codeServerPage.address()
9+
const address = await getMaybeProxiedCodeServer(codeServerPage)
810

911
await codeServerPage.executeCommandViaMenus("code-server: Get proxy URI")
1012

11-
await codeServerPage.page.waitForSelector(`text=${address}/proxy/{{port}}`)
13+
const text = await codeServerPage.page.locator(".notification-list-item-message").textContent()
14+
// Remove end slash in address
15+
const normalizedAddress = address.replace(/\/+$/, "")
16+
expect(text).toBe(`${normalizedAddress}/proxy/{{port}}`)
1217
})
1318
}
1419

1520
const flags = ["--extensions-dir", path.join(__dirname, "./extensions")]
1621

17-
describe("Extensions", true, flags, {}, () => {
22+
describe("Extensions", flags, {}, () => {
1823
runTestExtensionTests()
1924
})
2025

21-
describe("Extensions with --cert", true, [...flags, "--cert"], {}, () => {
22-
runTestExtensionTests()
23-
})
26+
if (process.env.USE_PROXY !== "1") {
27+
describe("Extensions with --cert", [...flags, "--cert"], {}, () => {
28+
runTestExtensionTests()
29+
})
30+
} else {
31+
base.describe("Extensions with --cert", () => {
32+
base.skip("skipped because USE_PROXY is set", () => {
33+
// Playwright will not show this without a function.
34+
})
35+
})
36+
}

test/e2e/github.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { test as base } from "@playwright/test"
22
import { describe, expect, test } from "./baseFixture"
33

44
if (process.env.GITHUB_TOKEN) {
5-
describe("GitHub token", true, [], {}, () => {
5+
describe("GitHub token", [], {}, () => {
66
test("should be logged in to pull requests extension", async ({ codeServerPage }) => {
77
await codeServerPage.exec("git init")
88
await codeServerPage.exec("git remote add origin https://github.com/coder/code-server")
@@ -16,7 +16,7 @@ if (process.env.GITHUB_TOKEN) {
1616
})
1717
})
1818

19-
describe("No GitHub token", true, [], { GITHUB_TOKEN: "" }, () => {
19+
describe("No GitHub token", [], { GITHUB_TOKEN: "" }, () => {
2020
test("should not be logged in to pull requests extension", async ({ codeServerPage }) => {
2121
await codeServerPage.exec("git init")
2222
await codeServerPage.exec("git remote add origin https://github.com/coder/code-server")

test/e2e/globalSetup.test.ts

-10
This file was deleted.

test/e2e/login.test.ts

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

4-
describe("login", false, [], {}, () => {
4+
describe("login", ["--auth", "password"], {}, () => {
55
test("should see the login page", async ({ codeServerPage }) => {
66
// It should send us to the login page
77
expect(await codeServerPage.page.title()).toBe("code-server login")

0 commit comments

Comments
 (0)