refactor: create test/utils
This commit is contained in:
4
test/utils/constants.ts
Normal file
4
test/utils/constants.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const CODE_SERVER_ADDRESS = process.env.CODE_SERVER_ADDRESS || "http://localhost:8080"
|
||||
export const PASSWORD = process.env.PASSWORD || "e45432jklfdsab"
|
||||
export const STORAGE = process.env.STORAGE || ""
|
||||
export const E2E_VIDEO_DIR = "./test/e2e/videos"
|
5
test/utils/cssStub.ts
Normal file
5
test/utils/cssStub.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// Note: this is needed for the register.test.ts
|
||||
// This is because inside src/browser/register.ts
|
||||
// we import CSS files, which Jest can't handle unless we tell it how to
|
||||
// See: https://stackoverflow.com/a/39434579/3015595
|
||||
module.exports = {}
|
34
test/utils/globalSetup.ts
Normal file
34
test/utils/globalSetup.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// This setup runs before our e2e tests
|
||||
// so that it authenticates us into code-server
|
||||
// ensuring that we're logged in before we run any tests
|
||||
import { chromium } from "playwright"
|
||||
import { CODE_SERVER_ADDRESS, PASSWORD } from "./constants"
|
||||
import * as wtfnode from "./wtfnode"
|
||||
|
||||
module.exports = async () => {
|
||||
console.log("\n🚨 Running Global Setup for Jest End-to-End Tests")
|
||||
console.log(" Please hang tight...")
|
||||
const browser = await chromium.launch()
|
||||
const context = await browser.newContext()
|
||||
const page = await context.newPage()
|
||||
|
||||
if (process.env.WTF_NODE) {
|
||||
wtfnode.setup()
|
||||
}
|
||||
|
||||
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "domcontentloaded" })
|
||||
// Type in password
|
||||
await page.fill(".password", PASSWORD)
|
||||
// Click the submit button and login
|
||||
await page.click(".submit")
|
||||
|
||||
// Save storage state and store as an env variable
|
||||
// More info: https://playwright.dev/docs/auth?_highlight=authe#reuse-authentication-state
|
||||
const storage = await context.storageState()
|
||||
process.env.STORAGE = JSON.stringify(storage)
|
||||
|
||||
await page.close()
|
||||
await browser.close()
|
||||
await context.close()
|
||||
console.log("✅ Global Setup for Jest End-to-End Tests is now complete.")
|
||||
}
|
47
test/utils/helpers.ts
Normal file
47
test/utils/helpers.ts
Normal file
@ -0,0 +1,47 @@
|
||||
// Borrowed from playwright
|
||||
export interface Cookie {
|
||||
name: string
|
||||
value: string
|
||||
domain: string
|
||||
path: string
|
||||
/**
|
||||
* Unix time in seconds.
|
||||
*/
|
||||
expires: number
|
||||
httpOnly: boolean
|
||||
secure: boolean
|
||||
sameSite: "Strict" | "Lax" | "None"
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a cookie exists in array of cookies
|
||||
*/
|
||||
export function checkForCookie(cookies: Array<Cookie>, key: string): boolean {
|
||||
// Check for a cookie where the name is equal to key
|
||||
return Boolean(cookies.find((cookie) => cookie.name === key))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a login cookie if one doesn't already exist
|
||||
*/
|
||||
export function createCookieIfDoesntExist(cookies: Array<Cookie>, cookieToStore: Cookie): Array<Cookie> {
|
||||
const cookieName = cookieToStore.name
|
||||
const doesCookieExist = checkForCookie(cookies, cookieName)
|
||||
if (!doesCookieExist) {
|
||||
const updatedCookies = [...cookies, cookieToStore]
|
||||
return updatedCookies
|
||||
}
|
||||
return cookies
|
||||
}
|
||||
|
||||
export const loggerModule = {
|
||||
field: jest.fn(),
|
||||
level: 2,
|
||||
logger: {
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
info: jest.fn(),
|
||||
trace: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
},
|
||||
}
|
113
test/utils/httpserver.ts
Normal file
113
test/utils/httpserver.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import * as express from "express"
|
||||
import * as http from "http"
|
||||
import * as net from "net"
|
||||
import * as nodeFetch from "node-fetch"
|
||||
import Websocket from "ws"
|
||||
import * as util from "../../src/common/util"
|
||||
import { ensureAddress } from "../../src/node/app"
|
||||
import { handleUpgrade } from "../../src/node/wsRouter"
|
||||
|
||||
// Perhaps an abstraction similar to this should be used in app.ts as well.
|
||||
export class HttpServer {
|
||||
private readonly sockets = new Set<net.Socket>()
|
||||
private cleanupTimeout?: NodeJS.Timeout
|
||||
|
||||
// See usage in test/integration.ts
|
||||
public constructor(private readonly hs = http.createServer()) {
|
||||
this.hs.on("connection", (socket) => {
|
||||
this.sockets.add(socket)
|
||||
socket.on("close", () => {
|
||||
this.sockets.delete(socket)
|
||||
if (this.cleanupTimeout && this.sockets.size === 0) {
|
||||
clearTimeout(this.cleanupTimeout)
|
||||
this.cleanupTimeout = undefined
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* listen starts the server on a random localhost port.
|
||||
* Use close to cleanup when done.
|
||||
*/
|
||||
public listen(fn: http.RequestListener): Promise<void> {
|
||||
this.hs.on("request", fn)
|
||||
|
||||
let resolved = false
|
||||
return new Promise((res, rej) => {
|
||||
this.hs.listen(0, "localhost", () => {
|
||||
res()
|
||||
resolved = true
|
||||
})
|
||||
|
||||
this.hs.on("error", (err) => {
|
||||
if (!resolved) {
|
||||
rej(err)
|
||||
} else {
|
||||
// Promise resolved earlier so this is some other error.
|
||||
util.logError("http server error", err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send upgrade requests to an Express app.
|
||||
*/
|
||||
public listenUpgrade(app: express.Express): void {
|
||||
handleUpgrade(app, this.hs)
|
||||
}
|
||||
|
||||
/**
|
||||
* close cleans up the server.
|
||||
*/
|
||||
public close(): Promise<void> {
|
||||
return new Promise((res, rej) => {
|
||||
// Close will not actually close anything; it just waits until everything
|
||||
// is closed.
|
||||
this.hs.close((err) => {
|
||||
if (err) {
|
||||
rej(err)
|
||||
return
|
||||
}
|
||||
res()
|
||||
})
|
||||
|
||||
// If there are sockets remaining we might need to force close them or
|
||||
// this promise might never resolve.
|
||||
if (this.sockets.size > 0) {
|
||||
// Give sockets a chance to close up shop.
|
||||
this.cleanupTimeout = setTimeout(() => {
|
||||
this.cleanupTimeout = undefined
|
||||
for (const socket of this.sockets.values()) {
|
||||
console.warn("a socket was left hanging")
|
||||
socket.destroy()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch fetches the request path.
|
||||
* The request path must be rooted!
|
||||
*/
|
||||
public fetch(requestPath: string, opts?: nodeFetch.RequestInit): Promise<nodeFetch.Response> {
|
||||
return nodeFetch.default(`${ensureAddress(this.hs)}${requestPath}`, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a websocket against the requset path.
|
||||
*/
|
||||
public ws(requestPath: string): Websocket {
|
||||
return new Websocket(`${ensureAddress(this.hs).replace("http:", "ws:")}${requestPath}`)
|
||||
}
|
||||
|
||||
public port(): number {
|
||||
const addr = this.hs.address()
|
||||
if (addr && typeof addr === "object") {
|
||||
return addr.port
|
||||
}
|
||||
throw new Error("server not listening or listening on unix socket")
|
||||
}
|
||||
}
|
35
test/utils/wtfnode.ts
Normal file
35
test/utils/wtfnode.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import * as util from "util"
|
||||
import * as wtfnode from "wtfnode"
|
||||
|
||||
// Jest seems to hijack console.log in a way that makes the output difficult to
|
||||
// read. So we'll write directly to process.stderr instead.
|
||||
const write = (...args: [any, ...any]) => {
|
||||
if (args.length > 0) {
|
||||
process.stderr.write(util.format(...args) + "\n")
|
||||
}
|
||||
}
|
||||
wtfnode.setLogger("info", write)
|
||||
wtfnode.setLogger("warn", write)
|
||||
wtfnode.setLogger("error", write)
|
||||
|
||||
let active = false
|
||||
|
||||
/**
|
||||
* Start logging open handles periodically. This can be used to see what is
|
||||
* hanging open if anything.
|
||||
*/
|
||||
export function setup(): void {
|
||||
if (active) {
|
||||
return
|
||||
}
|
||||
active = true
|
||||
|
||||
const interval = 5000
|
||||
const wtfnodeDump = () => {
|
||||
wtfnode.dump()
|
||||
const t = setTimeout(wtfnodeDump, interval)
|
||||
t.unref()
|
||||
}
|
||||
const t = setTimeout(wtfnodeDump, interval)
|
||||
t.unref()
|
||||
}
|
Reference in New Issue
Block a user