chore: upgrade to Playwright 1.12 with its new test-runner
This commit is contained in:
12
test/e2e/baseFixture.ts
Normal file
12
test/e2e/baseFixture.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { test as base } from "@playwright/test"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
|
||||
export const test = base.extend<{ codeServerPage: CodeServer }>({
|
||||
codeServerPage: async ({ page }, use) => {
|
||||
const codeServer = new CodeServer(page)
|
||||
await codeServer.navigate()
|
||||
await use(codeServer)
|
||||
},
|
||||
})
|
||||
|
||||
export const expect = test.expect
|
@ -1,22 +1,14 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
import { test, expect } from "./baseFixture"
|
||||
|
||||
// This is a "gut-check" test to make sure playwright is working as expected
|
||||
test.describe("browser", () => {
|
||||
let codeServer: CodeServer
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
codeServer = new CodeServer(page)
|
||||
await codeServer.navigate()
|
||||
})
|
||||
|
||||
test("browser should display correct userAgent", async ({ page, browserName }) => {
|
||||
test("browser should display correct userAgent", async ({ codeServerPage, browserName }) => {
|
||||
const displayNames = {
|
||||
chromium: "Chrome",
|
||||
firefox: "Firefox",
|
||||
webkit: "Safari",
|
||||
}
|
||||
const userAgent = await page.evaluate("navigator.userAgent")
|
||||
const userAgent = await codeServerPage.page.evaluate("navigator.userAgent")
|
||||
|
||||
expect(userAgent).toContain(displayNames[browserName])
|
||||
})
|
||||
|
@ -1,49 +1,32 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
import { CODE_SERVER_ADDRESS, storageState } from "../utils/constants"
|
||||
import { test, expect } from "./baseFixture"
|
||||
|
||||
test.describe("CodeServer", () => {
|
||||
// Create a new context with the saved storage state
|
||||
// so we don't have to logged in
|
||||
const options: any = {}
|
||||
let codeServer: CodeServer
|
||||
|
||||
// TODO@jsjoeio
|
||||
// Fix this once https://github.com/microsoft/playwright-test/issues/240
|
||||
// is fixed
|
||||
if (STORAGE) {
|
||||
const storageState = JSON.parse(STORAGE) || {}
|
||||
options.contextOptions = {
|
||||
storageState,
|
||||
}
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
codeServer = new CodeServer(page)
|
||||
await codeServer.setup()
|
||||
test.use({
|
||||
storageState,
|
||||
})
|
||||
|
||||
test(`should navigate to ${CODE_SERVER_ADDRESS}`, options, async ({ page }) => {
|
||||
test(`should navigate to ${CODE_SERVER_ADDRESS}`, async ({ codeServerPage }) => {
|
||||
// We navigate codeServer before each test
|
||||
// and we start the test with a storage state
|
||||
// which means we should be logged in
|
||||
// so it should be on the address
|
||||
const url = page.url()
|
||||
const url = codeServerPage.page.url()
|
||||
// We use match because there may be a / at the end
|
||||
// so we don't want it to fail if we expect http://localhost:8080 to match http://localhost:8080/
|
||||
expect(url).toMatch(CODE_SERVER_ADDRESS)
|
||||
})
|
||||
|
||||
test("should always see the code-server editor", options, async ({ page }) => {
|
||||
expect(await codeServer.isEditorVisible()).toBe(true)
|
||||
test("should always see the code-server editor", async ({ codeServerPage }) => {
|
||||
expect(await codeServerPage.isEditorVisible()).toBe(true)
|
||||
})
|
||||
|
||||
test("should always have a connection", options, async ({ page }) => {
|
||||
expect(await codeServer.isConnected()).toBe(true)
|
||||
test("should always have a connection", async ({ codeServerPage }) => {
|
||||
expect(await codeServerPage.isConnected()).toBe(true)
|
||||
})
|
||||
|
||||
test("should show the Integrated Terminal", options, async ({ page }) => {
|
||||
await codeServer.focusTerminal()
|
||||
expect(await page.isVisible("#terminal")).toBe(true)
|
||||
test("should show the Integrated Terminal", async ({ codeServerPage }) => {
|
||||
await codeServerPage.focusTerminal()
|
||||
expect(await codeServerPage.page.isVisible("#terminal")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
@ -1,30 +1,15 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { STORAGE } from "../utils/constants"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
import { storageState } from "../utils/constants"
|
||||
import { test, expect } from "./baseFixture"
|
||||
|
||||
// This test is to make sure the globalSetup works as expected
|
||||
// meaning globalSetup ran and stored the storageState in STORAGE
|
||||
// meaning globalSetup ran and stored the storageState
|
||||
test.describe("globalSetup", () => {
|
||||
// Create a new context with the saved storage state
|
||||
// so we don't have to logged in
|
||||
const options: any = {}
|
||||
let codeServer: CodeServer
|
||||
|
||||
// TODO@jsjoeio
|
||||
// Fix this once https://github.com/microsoft/playwright-test/issues/240
|
||||
// is fixed
|
||||
if (STORAGE) {
|
||||
const storageState = JSON.parse(STORAGE) || {}
|
||||
options.contextOptions = {
|
||||
storageState,
|
||||
}
|
||||
}
|
||||
test.beforeEach(async ({ page }) => {
|
||||
codeServer = new CodeServer(page)
|
||||
await codeServer.setup()
|
||||
test.use({
|
||||
storageState,
|
||||
})
|
||||
test("should keep us logged in using the storageState", options, async ({ page }) => {
|
||||
|
||||
test("should keep us logged in using the storageState", async ({ codeServerPage }) => {
|
||||
// Make sure the editor actually loaded
|
||||
expect(await codeServer.isEditorVisible()).toBe(true)
|
||||
expect(await codeServerPage.isEditorVisible()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
@ -1,76 +1,67 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { PASSWORD } from "../utils/constants"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
import { test, expect } from "./baseFixture"
|
||||
|
||||
test.describe("login", () => {
|
||||
// Reset the browser so no cookies are persisted
|
||||
// by emptying the storageState
|
||||
const options = {
|
||||
contextOptions: {
|
||||
storageState: {},
|
||||
},
|
||||
}
|
||||
let codeServer: CodeServer
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
codeServer = new CodeServer(page)
|
||||
await codeServer.navigate()
|
||||
test.use({
|
||||
storageState: {},
|
||||
})
|
||||
|
||||
test("should see the login page", options, async ({ page }) => {
|
||||
test("should see the login page", async ({ codeServerPage }) => {
|
||||
// It should send us to the login page
|
||||
expect(await page.title()).toBe("code-server login")
|
||||
expect(await codeServerPage.page.title()).toBe("code-server login")
|
||||
})
|
||||
|
||||
test("should be able to login", options, async ({ page }) => {
|
||||
test("should be able to login", async ({ codeServerPage }) => {
|
||||
// Type in password
|
||||
await page.fill(".password", PASSWORD)
|
||||
await codeServerPage.page.fill(".password", PASSWORD)
|
||||
// Click the submit button and login
|
||||
await page.click(".submit")
|
||||
await page.waitForLoadState("networkidle")
|
||||
await codeServerPage.page.click(".submit")
|
||||
await codeServerPage.page.waitForLoadState("networkidle")
|
||||
// We do this because occassionally code-server doesn't load on Firefox
|
||||
// but loads if you reload once or twice
|
||||
await codeServer.reloadUntilEditorIsReady()
|
||||
await codeServerPage.reloadUntilEditorIsReady()
|
||||
// Make sure the editor actually loaded
|
||||
expect(await codeServer.isEditorVisible()).toBe(true)
|
||||
expect(await codeServerPage.isEditorVisible()).toBe(true)
|
||||
})
|
||||
|
||||
test("should see an error message for missing password", options, async ({ page }) => {
|
||||
test("should see an error message for missing password", async ({ codeServerPage }) => {
|
||||
// Skip entering password
|
||||
// Click the submit button and login
|
||||
await page.click(".submit")
|
||||
await page.waitForLoadState("networkidle")
|
||||
expect(await page.isVisible("text=Missing password"))
|
||||
await codeServerPage.page.click(".submit")
|
||||
await codeServerPage.page.waitForLoadState("networkidle")
|
||||
expect(await codeServerPage.page.isVisible("text=Missing password"))
|
||||
})
|
||||
|
||||
test("should see an error message for incorrect password", options, async ({ page }) => {
|
||||
test("should see an error message for incorrect password", async ({ codeServerPage }) => {
|
||||
// Type in password
|
||||
await page.fill(".password", "password123")
|
||||
await codeServerPage.page.fill(".password", "password123")
|
||||
// Click the submit button and login
|
||||
await page.click(".submit")
|
||||
await page.waitForLoadState("networkidle")
|
||||
expect(await page.isVisible("text=Incorrect password"))
|
||||
await codeServerPage.page.click(".submit")
|
||||
await codeServerPage.page.waitForLoadState("networkidle")
|
||||
expect(await codeServerPage.page.isVisible("text=Incorrect password"))
|
||||
})
|
||||
|
||||
test("should hit the rate limiter for too many unsuccessful logins", options, async ({ page }) => {
|
||||
test("should hit the rate limiter for too many unsuccessful logins", async ({ codeServerPage }) => {
|
||||
// Type in password
|
||||
await page.fill(".password", "password123")
|
||||
await codeServerPage.page.fill(".password", "password123")
|
||||
// Click the submit button and login
|
||||
// The current RateLimiter allows 2 logins per minute plus
|
||||
// 12 logins per hour for a total of 14
|
||||
// See: src/node/routes/login.ts
|
||||
for (let i = 1; i <= 14; i++) {
|
||||
await page.click(".submit")
|
||||
await page.waitForLoadState("networkidle")
|
||||
await codeServerPage.page.click(".submit")
|
||||
await codeServerPage.page.waitForLoadState("networkidle")
|
||||
// We double-check that the correct error message shows
|
||||
// which should be for incorrect password
|
||||
expect(await page.isVisible("text=Incorrect password"))
|
||||
expect(await codeServerPage.page.isVisible("text=Incorrect password"))
|
||||
}
|
||||
|
||||
// The 15th should fail for a different reason:
|
||||
// login rate
|
||||
await page.click(".submit")
|
||||
await page.waitForLoadState("networkidle")
|
||||
expect(await page.isVisible("text=Login rate limited!"))
|
||||
await codeServerPage.page.click(".submit")
|
||||
await codeServerPage.page.waitForLoadState("networkidle")
|
||||
expect(await codeServerPage.page.isVisible("text=Login rate limited!"))
|
||||
})
|
||||
})
|
||||
|
@ -1,53 +1,44 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
import { test, expect } from "./baseFixture"
|
||||
|
||||
test.describe("logout", () => {
|
||||
// Reset the browser so no cookies are persisted
|
||||
// by emptying the storageState
|
||||
const options = {
|
||||
contextOptions: {
|
||||
storageState: {},
|
||||
},
|
||||
}
|
||||
let codeServer: CodeServer
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
codeServer = new CodeServer(page)
|
||||
await codeServer.navigate()
|
||||
test.use({
|
||||
storageState: {},
|
||||
})
|
||||
|
||||
test("should be able login and logout", options, async ({ page }) => {
|
||||
test("should be able login and logout", async ({ codeServerPage }) => {
|
||||
// Type in password
|
||||
await page.fill(".password", PASSWORD)
|
||||
await codeServerPage.page.fill(".password", PASSWORD)
|
||||
// Click the submit button and login
|
||||
await page.click(".submit")
|
||||
await page.waitForLoadState("networkidle")
|
||||
await codeServerPage.page.click(".submit")
|
||||
await codeServerPage.page.waitForLoadState("networkidle")
|
||||
// We do this because occassionally code-server doesn't load on Firefox
|
||||
// but loads if you reload once or twice
|
||||
await codeServer.reloadUntilEditorIsReady()
|
||||
await codeServerPage.reloadUntilEditorIsReady()
|
||||
// Make sure the editor actually loaded
|
||||
expect(await codeServer.isEditorVisible()).toBe(true)
|
||||
expect(await codeServerPage.isEditorVisible()).toBe(true)
|
||||
|
||||
// Click the Application menu
|
||||
await page.click("[aria-label='Application Menu']")
|
||||
await codeServerPage.page.click("[aria-label='Application Menu']")
|
||||
|
||||
// See the Log out button
|
||||
const logoutButton = "a.action-menu-item span[aria-label='Log out']"
|
||||
expect(await page.isVisible(logoutButton)).toBe(true)
|
||||
expect(await codeServerPage.page.isVisible(logoutButton)).toBe(true)
|
||||
|
||||
await page.hover(logoutButton)
|
||||
await codeServerPage.page.hover(logoutButton)
|
||||
// TODO(@jsjoeio)
|
||||
// Look into how we're attaching the handlers for the logout feature
|
||||
// We need to see how it's done upstream and add logging to the
|
||||
// handlers themselves.
|
||||
// They may be attached too slowly, hence why we need this timeout
|
||||
await page.waitForTimeout(2000)
|
||||
await codeServerPage.page.waitForTimeout(2000)
|
||||
|
||||
// Recommended by Playwright for async navigation
|
||||
// https://github.com/microsoft/playwright/issues/1987#issuecomment-620182151
|
||||
await Promise.all([page.waitForNavigation(), page.click(logoutButton)])
|
||||
const currentUrl = page.url()
|
||||
await Promise.all([codeServerPage.page.waitForNavigation(), codeServerPage.page.click(logoutButton)])
|
||||
const currentUrl = codeServerPage.page.url()
|
||||
expect(currentUrl).toBe(`${CODE_SERVER_ADDRESS}/login`)
|
||||
})
|
||||
})
|
||||
|
@ -41,7 +41,7 @@ export class CodeServer {
|
||||
// Give it an extra second just in case it's feeling extra slow
|
||||
await this.page.waitForTimeout(1000)
|
||||
reloadCount += 1
|
||||
if ((await this.isEditorVisible()) && (await this.isConnected)) {
|
||||
if ((await this.isEditorVisible()) && (await this.isConnected())) {
|
||||
console.log(` Editor became ready after ${reloadCount} reloads`)
|
||||
break
|
||||
}
|
||||
@ -54,8 +54,7 @@ export class CodeServer {
|
||||
*/
|
||||
async isEditorVisible() {
|
||||
// Make sure the editor actually loaded
|
||||
// If it's not visible after 5 seconds, something is wrong
|
||||
await this.page.waitForLoadState("networkidle")
|
||||
await this.page.waitForSelector(this.editorSelector)
|
||||
return await this.page.isVisible(this.editorSelector)
|
||||
}
|
||||
|
||||
|
@ -1,46 +1,28 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { STORAGE } from "../utils/constants"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
import { storageState } from "../utils/constants"
|
||||
import { test, expect } from "./baseFixture"
|
||||
|
||||
test.describe("Open Help > About", () => {
|
||||
// Create a new context with the saved storage state
|
||||
// so we don't have to logged in
|
||||
const options: any = {}
|
||||
let codeServer: CodeServer
|
||||
// TODO@jsjoeio
|
||||
// Fix this once https://github.com/microsoft/playwright-test/issues/240
|
||||
// is fixed
|
||||
if (STORAGE) {
|
||||
const storageState = JSON.parse(STORAGE) || {}
|
||||
options.contextOptions = {
|
||||
storageState,
|
||||
}
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
codeServer = new CodeServer(page)
|
||||
await codeServer.setup()
|
||||
test.use({
|
||||
storageState,
|
||||
})
|
||||
|
||||
test(
|
||||
"should see a 'Help' then 'About' button in the Application Menu that opens a dialog",
|
||||
options,
|
||||
async ({ page }) => {
|
||||
// Open using the manu
|
||||
// Click [aria-label="Application Menu"] div[role="none"]
|
||||
await page.click('[aria-label="Application Menu"] div[role="none"]')
|
||||
test("should see a 'Help' then 'About' button in the Application Menu that opens a dialog", async ({
|
||||
codeServerPage,
|
||||
}) => {
|
||||
// Open using the manu
|
||||
// Click [aria-label="Application Menu"] div[role="none"]
|
||||
await codeServerPage.page.click('[aria-label="Application Menu"] div[role="none"]')
|
||||
|
||||
// Click the Help button
|
||||
await page.hover("text=Help")
|
||||
await page.click("text=Help")
|
||||
// Click the Help button
|
||||
await codeServerPage.page.hover("text=Help")
|
||||
await codeServerPage.page.click("text=Help")
|
||||
|
||||
// Click the About button
|
||||
await page.hover("text=About")
|
||||
await page.click("text=About")
|
||||
// Click the About button
|
||||
await codeServerPage.page.hover("text=About")
|
||||
await codeServerPage.page.click("text=About")
|
||||
|
||||
// Click div[role="dialog"] >> text=code-server
|
||||
const element = await page.waitForSelector('div[role="dialog"] >> text=code-server')
|
||||
expect(element).not.toBeNull()
|
||||
},
|
||||
)
|
||||
// Click div[role="dialog"] >> text=code-server
|
||||
const element = await codeServerPage.page.waitForSelector('div[role="dialog"] >> text=code-server')
|
||||
expect(element).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
@ -1,59 +1,46 @@
|
||||
import { expect, test } from "@playwright/test"
|
||||
import * as cp from "child_process"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import util from "util"
|
||||
import { STORAGE } from "../utils/constants"
|
||||
import { storageState } from "../utils/constants"
|
||||
import { tmpdir } from "../utils/helpers"
|
||||
import { CodeServer } from "./models/CodeServer"
|
||||
import { expect, test } from "./baseFixture"
|
||||
|
||||
test.describe("Integrated Terminal", () => {
|
||||
// Create a new context with the saved storage state
|
||||
// so we don't have to logged in
|
||||
const options: any = {}
|
||||
const testFileName = "pipe"
|
||||
const testString = "new string test from e2e test"
|
||||
let codeServer: CodeServer
|
||||
let tmpFolderPath = ""
|
||||
let tmpFile = ""
|
||||
|
||||
// TODO@jsjoeio
|
||||
// Fix this once https://github.com/microsoft/playwright-test/issues/240
|
||||
// is fixed
|
||||
if (STORAGE) {
|
||||
const storageState = JSON.parse(STORAGE) || {}
|
||||
options.contextOptions = {
|
||||
storageState,
|
||||
}
|
||||
}
|
||||
test.use({
|
||||
storageState,
|
||||
})
|
||||
|
||||
test.beforeAll(async () => {
|
||||
tmpFolderPath = await tmpdir("integrated-terminal")
|
||||
tmpFile = path.join(tmpFolderPath, testFileName)
|
||||
})
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
codeServer = new CodeServer(page)
|
||||
await codeServer.setup()
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
// Ensure directory was removed
|
||||
await fs.promises.rmdir(tmpFolderPath, { recursive: true })
|
||||
})
|
||||
|
||||
test("should echo a string to a file", options, async ({ page }) => {
|
||||
test("should echo a string to a file", async ({ codeServerPage }) => {
|
||||
const command = `mkfifo '${tmpFile}' && cat '${tmpFile}'`
|
||||
const exec = util.promisify(cp.exec)
|
||||
const output = exec(command, { encoding: "utf8" })
|
||||
|
||||
// Open terminal and type in value
|
||||
await codeServer.focusTerminal()
|
||||
await codeServerPage.focusTerminal()
|
||||
|
||||
await page.waitForLoadState("load")
|
||||
await page.keyboard.type(`echo ${testString} > ${tmpFile}`)
|
||||
await page.keyboard.press("Enter")
|
||||
await codeServerPage.page.waitForLoadState("load")
|
||||
await codeServerPage.page.keyboard.type(`echo ${testString} > ${tmpFile}`)
|
||||
await codeServerPage.page.keyboard.press("Enter")
|
||||
// It may take a second to process
|
||||
await page.waitForTimeout(1000)
|
||||
await codeServerPage.page.waitForTimeout(1000)
|
||||
|
||||
const { stdout } = await output
|
||||
expect(stdout).toMatch(testString)
|
||||
|
Reference in New Issue
Block a user