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 07d6823

Browse files
authored
Merge pull request #3169 from cdr/jsjoeio/add-terminal-e2e-test
feat(testing): add e2e tests for code-server and terminal
2 parents 85ded73 + 7bfdd13 commit 07d6823

16 files changed

+315
-50
lines changed

‎src/node/constants.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { logger } from "@coder/logger"
22
import { JSONSchemaForNPMPackageJsonFiles } from "@schemastore/package"
3+
import * as os from "os"
34
import * as path from "path"
45

56
export function getPackageJson(relativePath: string): JSONSchemaForNPMPackageJsonFiles {
@@ -18,3 +19,4 @@ const pkg = getPackageJson("../../package.json")
1819
export const version = pkg.version || "development"
1920
export const commit = pkg.commit || "development"
2021
export const rootPath = path.resolve(__dirname, "../..")
22+
export const tmpdir = path.join(os.tmpdir(), "code-server")

‎src/node/socket.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as path from "path"
44
import * as tls from "tls"
55
import { Emitter } from "../common/emitter"
66
import { generateUuid } from "../common/util"
7-
import { canConnect, tmpdir } from "./util"
7+
import { tmpdir } from "./constants"
8+
import { canConnect } from "./util"
89

910
/**
1011
* Provides a way to proxy a TLS socket. Can be used when you need to pass a

‎src/node/util.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import * as path from "path"
88
import * as util from "util"
99
import xdgBasedir from "xdg-basedir"
1010

11-
export const tmpdir = path.join(os.tmpdir(), "code-server")
12-
1311
interface Paths {
1412
data: string
1513
config: string

‎test/e2e/browser.test.ts‎

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import { test, expect } from "@playwright/test"
2-
import { CODE_SERVER_ADDRESS } from "../utils/constants"
2+
import { CodeServer } from "./models/CodeServer"
33

44
// This is a "gut-check" test to make sure playwright is working as expected
5-
test("browser should display correct userAgent", async ({ page, browserName }) => {
6-
const displayNames = {
7-
chromium: "Chrome",
8-
firefox: "Firefox",
9-
webkit: "Safari",
10-
}
11-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
12-
const userAgent = await page.evaluate("navigator.userAgent")
13-
14-
expect(userAgent).toContain(displayNames[browserName])
5+
test.describe("browser", () => {
6+
let codeServer: CodeServer
7+
8+
test.beforeEach(async ({ page }) => {
9+
codeServer = new CodeServer(page)
10+
await codeServer.navigate()
11+
})
12+
13+
test("browser should display correct userAgent", async ({ page, browserName }) => {
14+
const displayNames = {
15+
chromium: "Chrome",
16+
firefox: "Firefox",
17+
webkit: "Safari",
18+
}
19+
const userAgent = await page.evaluate("navigator.userAgent")
20+
21+
expect(userAgent).toContain(displayNames[browserName])
22+
})
1523
})

‎test/e2e/codeServer.test.ts‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { test, expect } from "@playwright/test"
2+
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants"
3+
import { CodeServer } from "./models/CodeServer"
4+
5+
test.describe("CodeServer", () => {
6+
// Create a new context with the saved storage state
7+
// so we don't have to logged in
8+
const options: any = {}
9+
let codeServer: CodeServer
10+
11+
// TODO@jsjoeio
12+
// Fix this once https://github.com/microsoft/playwright-test/issues/240
13+
// is fixed
14+
if (STORAGE) {
15+
const storageState = JSON.parse(STORAGE) || {}
16+
options.contextOptions = {
17+
storageState,
18+
}
19+
}
20+
21+
test.beforeEach(async ({ page }) => {
22+
codeServer = new CodeServer(page)
23+
await codeServer.setup()
24+
})
25+
26+
test(`should navigate to ${CODE_SERVER_ADDRESS}`, options, async ({ page }) => {
27+
// We navigate codeServer before each test
28+
// and we start the test with a storage state
29+
// which means we should be logged in
30+
// so it should be on the address
31+
const url = page.url()
32+
// We use match because there may be a / at the end
33+
// so we don't want it to fail if we expect http://localhost:8080 to match http://localhost:8080/
34+
expect(url).toMatch(CODE_SERVER_ADDRESS)
35+
})
36+
37+
test("should always see the code-server editor", options, async ({ page }) => {
38+
expect(await codeServer.isEditorVisible()).toBe(true)
39+
})
40+
41+
test("should show the Integrated Terminal", options, async ({ page }) => {
42+
await codeServer.focusTerminal()
43+
expect(await page.isVisible("#terminal")).toBe(true)
44+
})
45+
})

‎test/e2e/globalSetup.test.ts‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { test, expect } from "@playwright/test"
2-
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants"
2+
import { STORAGE } from "../utils/constants"
3+
import { CodeServer } from "./models/CodeServer"
34

45
// This test is to make sure the globalSetup works as expected
56
// meaning globalSetup ran and stored the storageState in STORAGE
67
test.describe("globalSetup", () => {
78
// Create a new context with the saved storage state
89
// so we don't have to logged in
910
const options: any = {}
11+
let codeServer: CodeServer
1012

1113
// TODO@jsjoeio
1214
// Fix this once https://github.com/microsoft/playwright-test/issues/240
@@ -17,9 +19,12 @@ test.describe("globalSetup", () => {
1719
storageState,
1820
}
1921
}
22+
test.beforeEach(async ({ page }) => {
23+
codeServer = new CodeServer(page)
24+
await codeServer.setup()
25+
})
2026
test("should keep us logged in using the storageState", options, async ({ page }) => {
21-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
2227
// Make sure the editor actually loaded
23-
expect(await page.isVisible("div.monaco-workbench"))
28+
expect(await codeServer.isEditorVisible()).toBe(true)
2429
})
2530
})

‎test/e2e/login.test.ts‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { test, expect } from "@playwright/test"
2-
import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants"
2+
import { PASSWORD } from "../utils/constants"
3+
import { CodeServer } from "./models/CodeServer"
34

45
test.describe("login", () => {
56
// Reset the browser so no cookies are persisted
@@ -9,26 +10,32 @@ test.describe("login", () => {
910
storageState: {},
1011
},
1112
}
13+
let codeServer: CodeServer
14+
15+
test.beforeEach(async ({ page }) => {
16+
codeServer = new CodeServer(page)
17+
await codeServer.navigate()
18+
})
1219

1320
test("should see the login page", options, async ({ page }) => {
14-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
1521
// It should send us to the login page
1622
expect(await page.title()).toBe("code-server login")
1723
})
1824

1925
test("should be able to login", options, async ({ page }) => {
20-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
2126
// Type in password
2227
await page.fill(".password", PASSWORD)
2328
// Click the submit button and login
2429
await page.click(".submit")
2530
await page.waitForLoadState("networkidle")
31+
// We do this because occassionally code-server doesn't load on Firefox
32+
// but loads if you reload once or twice
33+
await codeServer.reloadUntilEditorIsVisible()
2634
// Make sure the editor actually loaded
27-
expect(await page.isVisible("div.monaco-workbench"))
35+
expect(await codeServer.isEditorVisible()).toBe(true)
2836
})
2937

3038
test("should see an error message for missing password", options, async ({ page }) => {
31-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
3239
// Skip entering password
3340
// Click the submit button and login
3441
await page.click(".submit")
@@ -37,7 +44,6 @@ test.describe("login", () => {
3744
})
3845

3946
test("should see an error message for incorrect password", options, async ({ page }) => {
40-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
4147
// Type in password
4248
await page.fill(".password", "password123")
4349
// Click the submit button and login
@@ -47,7 +53,6 @@ test.describe("login", () => {
4753
})
4854

4955
test("should hit the rate limiter for too many unsuccessful logins", options, async ({ page }) => {
50-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
5156
// Type in password
5257
await page.fill(".password", "password123")
5358
// Click the submit button and login

‎test/e2e/logout.test.ts‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { test, expect } from "@playwright/test"
22
import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants"
3+
import { CodeServer } from "./models/CodeServer"
34

45
test.describe("logout", () => {
56
// Reset the browser so no cookies are persisted
@@ -9,22 +10,31 @@ test.describe("logout", () => {
910
storageState: {},
1011
},
1112
}
13+
let codeServer: CodeServer
14+
15+
test.beforeEach(async ({ page }) => {
16+
codeServer = new CodeServer(page)
17+
await codeServer.navigate()
18+
})
19+
1220
test("should be able login and logout", options, async ({ page }) => {
13-
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
1421
// Type in password
1522
await page.fill(".password", PASSWORD)
1623
// Click the submit button and login
1724
await page.click(".submit")
1825
await page.waitForLoadState("networkidle")
26+
// We do this because occassionally code-server doesn't load on Firefox
27+
// but loads if you reload once or twice
28+
await codeServer.reloadUntilEditorIsVisible()
1929
// Make sure the editor actually loaded
20-
expect(await page.isVisible("div.monaco-workbench"))
30+
expect(await codeServer.isEditorVisible()).toBe(true)
2131

2232
// Click the Application menu
2333
await page.click("[aria-label='Application Menu']")
2434

2535
// See the Log out button
2636
const logoutButton = "a.action-menu-item span[aria-label='Log out']"
27-
expect(await page.isVisible(logoutButton))
37+
expect(await page.isVisible(logoutButton)).toBe(true)
2838

2939
await page.hover(logoutButton)
3040
// TODO(@jsjoeio)

‎test/e2e/models/CodeServer.ts‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { Page } from "playwright"
2+
import { CODE_SERVER_ADDRESS } from "../../utils/constants"
3+
// This is a Page Object Model
4+
// We use these to simplify e2e test authoring
5+
// See Playwright docs: https://playwright.dev/docs/pom/
6+
export class CodeServer {
7+
page: Page
8+
9+
constructor(page: Page) {
10+
this.page = page
11+
}
12+
13+
/**
14+
* Navigates to CODE_SERVER_ADDRESS
15+
*/
16+
async navigate() {
17+
await this.page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" })
18+
}
19+
20+
/**
21+
* Checks if the editor is visible
22+
* and reloads until it is
23+
*/
24+
async reloadUntilEditorIsVisible() {
25+
const editorIsVisible = await this.isEditorVisible()
26+
let reloadCount = 0
27+
28+
// Occassionally code-server timeouts in Firefox
29+
// we're not sure why
30+
// but usually a reload or two fixes it
31+
// TODO@jsjoeio @oxy look into Firefox reconnection/timeout issues
32+
while (!editorIsVisible) {
33+
reloadCount += 1
34+
if (await this.isEditorVisible()) {
35+
console.log(` Editor became visible after ${reloadCount} reloads`)
36+
break
37+
}
38+
// When a reload happens, we want to wait for all resources to be
39+
// loaded completely. Hence why we use that instead of DOMContentLoaded
40+
// Read more: https://thisthat.dev/dom-content-loaded-vs-load/
41+
await this.page.reload({ waitUntil: "load" })
42+
}
43+
}
44+
45+
/**
46+
* Checks if the editor is visible
47+
*/
48+
async isEditorVisible() {
49+
// Make sure the editor actually loaded
50+
// If it's not visible after 5 seconds, something is wrong
51+
await this.page.waitForLoadState("networkidle")
52+
return await this.page.isVisible("div.monaco-workbench", { timeout: 5000 })
53+
}
54+
55+
/**
56+
* Focuses Integrated Terminal
57+
* by going to the Application Menu
58+
* and clicking View > Terminal
59+
*/
60+
async focusTerminal() {
61+
// If the terminal is already visible
62+
// then we can focus it by hitting the keyboard shortcut
63+
const isTerminalVisible = await this.page.isVisible("#terminal")
64+
if (isTerminalVisible) {
65+
await this.page.keyboard.press(`Control+Backquote`)
66+
// Wait for terminal to receive focus
67+
await this.page.waitForSelector("div.terminal.xterm.focus")
68+
// Sometimes the terminal reloads
69+
// which is why we wait for it twice
70+
await this.page.waitForSelector("div.terminal.xterm.focus")
71+
return
72+
}
73+
// Open using the manu
74+
// Click [aria-label="Application Menu"] div[role="none"]
75+
await this.page.click('[aria-label="Application Menu"] div[role="none"]')
76+
77+
// Click text=View
78+
await this.page.hover("text=View")
79+
await this.page.click("text=View")
80+
81+
// Click text=Terminal
82+
await this.page.hover("text=Terminal")
83+
await this.page.click("text=Terminal")
84+
85+
// Wait for terminal to receive focus
86+
// Sometimes the terminal reloads once or twice
87+
// which is why we wait for it to have the focus class
88+
await this.page.waitForSelector("div.terminal.xterm.focus")
89+
// Sometimes the terminal reloads
90+
// which is why we wait for it twice
91+
await this.page.waitForSelector("div.terminal.xterm.focus")
92+
}
93+
94+
/**
95+
* Navigates to CODE_SERVER_ADDRESS
96+
* and reloads until the editor is visible
97+
*
98+
* Helpful for running before tests
99+
*/
100+
async setup() {
101+
await this.navigate()
102+
await this.reloadUntilEditorIsVisible()
103+
}
104+
}

0 commit comments

Comments
(0)

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