Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7dbb72a

Browse files
authored
Merge pull request #2648 from cdr/e2e-test-go-home
feat(testing): add e2e test for 'Go Home' button
2 parents a2b2432 + 47a05c9 commit 7dbb72a

File tree

14 files changed

+310
-9
lines changed

14 files changed

+310
-9
lines changed

‎.github/workflows/ci.yaml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ jobs:
2424
test:
2525
needs: linux-amd64
2626
runs-on: ubuntu-latest
27+
env:
28+
PASSWORD: e45432jklfdsab
29+
CODE_SERVER_ADDRESS: http://localhost:8080
2730
steps:
2831
- uses: actions/checkout@v1
2932
- name: Download release packages
@@ -37,9 +40,14 @@ jobs:
3740
- uses: microsoft/playwright-github-action@v1
3841
- name: Install dependencies and run tests
3942
run: |
40-
node ./release-packages/code-server*-linux-amd64 &
43+
./release-packages/code-server*-linux-amd64/bin/code-server --home $CODE_SERVER_ADDRESS/healthz &
4144
yarn --frozen-lockfile
4245
yarn test
46+
- name: Upload test artifacts
47+
uses: actions/upload-artifact@v2
48+
with:
49+
name: test-videos
50+
path: ./test/videos
4351

4452
release:
4553
runs-on: ubuntu-latest

‎.gitignore‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ node-*
1515
/lib/coder-cloud-agent
1616
.home
1717
coverage
18-
**/.DS_Store
18+
**/.DS_Store
19+
test/videos
20+
test/screenshots

‎ci/dev/test.sh‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ main() {
99
# information. We must also run it from the root otherwise coverage will not
1010
# include our source files.
1111
cd "$OLDPWD"
12+
if [[ -z ${PASSWORD-} ]] || [[ -z ${CODE_SERVER_ADDRESS-} ]]; then
13+
echo "The end-to-end testing suites rely on your local environment"
14+
echo -e "\n"
15+
echo "Please set the following environment variables locally:"
16+
echo " \$PASSWORD"
17+
echo " \$CODE_SERVER_ADDRESS"
18+
echo -e "\n"
19+
echo "Please make sure you have code-server running locally with the flag:"
20+
echo " --home \$CODE_SERVER_ADDRESS/healthz "
21+
echo -e "\n"
22+
exit 1
23+
fi
1224
CS_DISABLE_PLUGINS=true ./test/node_modules/.bin/jest "$@"
1325
}
1426

‎package.json‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,16 @@
143143
"lines": 40
144144
}
145145
},
146+
"testTimeout": 30000,
147+
"globalSetup": "<rootDir>/test/globalSetup.ts",
146148
"modulePathIgnorePatterns": [
147-
"<rootDir>/release"
149+
"<rootDir>/lib/vscode",
150+
"<rootDir>/release-packages",
151+
"<rootDir>/release",
152+
"<rootDir>/release-standalone",
153+
"<rootDir>/release-npm-package",
154+
"<rootDir>/release-gcp",
155+
"<rootDir>/release-images"
148156
]
149157
}
150158
}

‎src/node/routes/login.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { rootPath } from "../constants"
77
import { authenticated, getCookieDomain, redirect, replaceTemplates } from "../http"
88
import { hash, humanPath } from "../util"
99

10-
enum Cookie {
10+
exportenum Cookie {
1111
Key = "key",
1212
}
1313

‎test/constants.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const CODE_SERVER_ADDRESS = process.env.CODE_SERVER_ADDRESS || "http://localhost:8080"
2+
export const PASSWORD = process.env.PASSWORD || "e45432jklfdsab"
3+
export const STORAGE = process.env.STORAGE || ""

‎test/e2e.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { chromium, Page, Browser } from "playwright"
2+
import { CODE_SERVER_ADDRESS } from "./constants"
23

34
let browser: Browser
45
let page: Page
@@ -17,7 +18,7 @@ afterEach(async () => {
1718
})
1819

1920
it("should see the login page", async () => {
20-
await page.goto("http://localhost:8080")
21+
await page.goto(CODE_SERVER_ADDRESS)
2122
// It should send us to the login page
2223
expect(await page.title()).toBe("code-server login")
2324
})

‎test/globalSetup.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// This setup runs before our e2e tests
2+
// so that it authenticates us into code-server
3+
// ensuring that we're logged in before we run any tests
4+
import { chromium } from "playwright"
5+
import { CODE_SERVER_ADDRESS, PASSWORD } from "./constants"
6+
import * as wtfnode from "./wtfnode"
7+
8+
module.exports = async () => {
9+
console.log("\n🚨 Running Global Setup for Jest Tests")
10+
console.log(" Please hang tight...")
11+
const browser = await chromium.launch()
12+
const context = await browser.newContext()
13+
const page = await context.newPage()
14+
15+
if (process.env.WTF_NODE) {
16+
wtfnode.setup()
17+
}
18+
19+
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "domcontentloaded" })
20+
// Type in password
21+
await page.fill(".password", PASSWORD)
22+
// Click the submit button and login
23+
await page.click(".submit")
24+
25+
// Save storage state and store as an env variable
26+
// More info: https://playwright.dev/docs/auth?_highlight=authe#reuse-authentication-state
27+
const storage = await context.storageState()
28+
process.env.STORAGE = JSON.stringify(storage)
29+
30+
await page.close()
31+
await browser.close()
32+
await context.close()
33+
console.log("✅ Global Setup for Jest Tests is now complete.")
34+
}

‎test/goHome.test.ts‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { chromium, Page, Browser, BrowserContext, Cookie } from "playwright"
2+
import { hash } from "../src/node/util"
3+
import { CODE_SERVER_ADDRESS, PASSWORD, STORAGE } from "./constants"
4+
import { createCookieIfDoesntExist } from "./helpers"
5+
6+
describe("go home", () => {
7+
let browser: Browser
8+
let page: Page
9+
let context: BrowserContext
10+
11+
beforeAll(async () => {
12+
browser = await chromium.launch()
13+
// Create a new context with the saved storage state
14+
const storageState = JSON.parse(STORAGE) || {}
15+
16+
const cookieToStore = {
17+
sameSite: "Lax" as const,
18+
name: "key",
19+
value: hash(PASSWORD),
20+
domain: "localhost",
21+
path: "/",
22+
expires: -1,
23+
httpOnly: false,
24+
secure: false,
25+
}
26+
27+
// For some odd reason, the login method used in globalSetup.ts doesn't always work
28+
// I don't know if it's on playwright clearing our cookies by accident
29+
// or if it's our cookies disappearing.
30+
// This means we need an additional check to make sure we're logged in.
31+
// We do this by manually adding the cookie to the browser environment
32+
// if it's not there at the time the test starts
33+
const cookies: Cookie[] = storageState.cookies || []
34+
// If the cookie exists in cookies then
35+
// this will return the cookies with no changes
36+
// otherwise if it doesn't exist, it will create it
37+
// hence the name maybeUpdatedCookies
38+
//
39+
// TODO(@jsjoeio)
40+
// The playwright storage thing sometimes works and sometimes doesn't. We should investigate this further
41+
// at some point.
42+
// See discussion: https://github.com/cdr/code-server/pull/2648#discussion_r575434946
43+
44+
const maybeUpdatedCookies = createCookieIfDoesntExist(cookies, cookieToStore)
45+
46+
context = await browser.newContext({
47+
storageState: { cookies: maybeUpdatedCookies },
48+
recordVideo: { dir: "./test/videos/" },
49+
})
50+
})
51+
52+
afterAll(async () => {
53+
// Remove password from local storage
54+
await context.clearCookies()
55+
56+
await context.close()
57+
await browser.close()
58+
})
59+
60+
beforeEach(async () => {
61+
page = await context.newPage()
62+
})
63+
64+
// NOTE: this test will fail if you do not run code-server with --home $CODE_SERVER_ADDRESS/healthz
65+
it("should see a 'Go Home' button in the Application Menu that goes to /healthz", async () => {
66+
const GO_HOME_URL = `${CODE_SERVER_ADDRESS}/healthz`
67+
// Sometimes a dialog shows up when you navigate
68+
// asking if you're sure you want to leave
69+
// so we listen if it comes, we accept it
70+
page.on("dialog", (dialog) => dialog.accept())
71+
72+
// waitUntil: "domcontentloaded"
73+
// In case the page takes a long time to load
74+
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "domcontentloaded" })
75+
76+
// Click the Home menu
77+
await page.click(".home-bar ul[aria-label='Home'] li")
78+
// See the Go Home button
79+
const goHomeButton = "a.action-menu-item span[aria-label='Go Home']"
80+
expect(await page.isVisible(goHomeButton))
81+
82+
// Click it and navigate to /healthz
83+
// NOTE: ran into issues of it failing intermittently
84+
// without having button: "middle"
85+
await Promise.all([page.waitForNavigation(), page.click(goHomeButton, { button: "middle" })])
86+
expect(page.url()).toBe(GO_HOME_URL)
87+
})
88+
})

‎test/helpers.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Borrowed from playwright
2+
export interface Cookie {
3+
name: string
4+
value: string
5+
domain: string
6+
path: string
7+
/**
8+
* Unix time in seconds.
9+
*/
10+
expires: number
11+
httpOnly: boolean
12+
secure: boolean
13+
sameSite: "Strict" | "Lax" | "None"
14+
}
15+
16+
/**
17+
* Checks if a cookie exists in array of cookies
18+
*/
19+
export function checkForCookie(cookies: Array<Cookie>, key: string): boolean {
20+
// Check for a cookie where the name is equal to key
21+
return Boolean(cookies.find((cookie) => cookie.name === key))
22+
}
23+
24+
/**
25+
* Creates a login cookie if one doesn't already exist
26+
*/
27+
export function createCookieIfDoesntExist(cookies: Array<Cookie>, cookieToStore: Cookie): Array<Cookie> {
28+
const cookieName = cookieToStore.name
29+
const doesCookieExist = checkForCookie(cookies, cookieName)
30+
if (!doesCookieExist) {
31+
const updatedCookies = [...cookies, cookieToStore]
32+
return updatedCookies
33+
}
34+
return cookies
35+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /