From b0ecff338ff41f17e5bbe2bbece51c386d7d295a Mon Sep 17 00:00:00 2001 From: Joe Previte Date: Wed, 21 Apr 2021 14:31:15 -0700 Subject: [PATCH] refactor: globalSetup to use CodeServer model --- test/e2e/browser.test.ts | 28 +++++++++++++++--------- test/e2e/codeServer.test.ts | 4 ++-- test/e2e/globalSetup.test.ts | 11 +++++++--- test/e2e/login.test.ts | 19 ++++++++++------ test/e2e/logout.test.ts | 14 ++++++++++-- test/e2e/models/CodeServer.ts | 32 ++++++++++++++++++++++++++- test/e2e/openHelpAbout.test.ts | 40 +++++++++++++++++----------------- test/e2e/terminal.test.ts | 39 +++++++++++++++++++++------------ 8 files changed, 128 insertions(+), 59 deletions(-) diff --git a/test/e2e/browser.test.ts b/test/e2e/browser.test.ts index c5537ba0..67952123 100644 --- a/test/e2e/browser.test.ts +++ b/test/e2e/browser.test.ts @@ -1,15 +1,23 @@ import { test, expect } from "@playwright/test" -import { CODE_SERVER_ADDRESS } from "../utils/constants" +import { CodeServer } from "./models/CodeServer" // This is a "gut-check" test to make sure playwright is working as expected -test("browser should display correct userAgent", async ({ page, browserName }) => { - const displayNames = { - chromium: "Chrome", - firefox: "Firefox", - webkit: "Safari", - } - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) - const userAgent = await page.evaluate("navigator.userAgent") +test.describe("browser", () => { + let codeServer: CodeServer - expect(userAgent).toContain(displayNames[browserName]) + test.beforeEach(async ({ page }) => { + codeServer = new CodeServer(page) + await codeServer.navigate() + }) + + test("browser should display correct userAgent", async ({ page, browserName }) => { + const displayNames = { + chromium: "Chrome", + firefox: "Firefox", + webkit: "Safari", + } + const userAgent = await page.evaluate("navigator.userAgent") + + expect(userAgent).toContain(displayNames[browserName]) + }) }) diff --git a/test/e2e/codeServer.test.ts b/test/e2e/codeServer.test.ts index d0e6ac92..4b20f69f 100644 --- a/test/e2e/codeServer.test.ts +++ b/test/e2e/codeServer.test.ts @@ -20,10 +20,10 @@ test.describe("CodeServer", () => { test.beforeEach(async ({ page }) => { codeServer = new CodeServer(page) - await codeServer.navigate() + await codeServer.setup() }) - test("should navigate to the CODE_SERVER_ADDRESS", options, async ({ page }) => { + test(`should navigate to ${CODE_SERVER_ADDRESS}`, options, async ({ page }) => { // We navigate codeServer before each test // and we start the test with a storage state // which means we should be logged in diff --git a/test/e2e/globalSetup.test.ts b/test/e2e/globalSetup.test.ts index 28d89bfc..0a950741 100644 --- a/test/e2e/globalSetup.test.ts +++ b/test/e2e/globalSetup.test.ts @@ -1,5 +1,6 @@ import { test, expect } from "@playwright/test" -import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants" +import { STORAGE } from "../utils/constants" +import { CodeServer } from "./models/CodeServer" // This test is to make sure the globalSetup works as expected // meaning globalSetup ran and stored the storageState in STORAGE @@ -7,6 +8,7 @@ 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 @@ -17,9 +19,12 @@ test.describe("globalSetup", () => { storageState, } } + test.beforeEach(async ({ page }) => { + codeServer = new CodeServer(page) + await codeServer.setup() + }) test("should keep us logged in using the storageState", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) // Make sure the editor actually loaded - expect(await page.isVisible("div.monaco-workbench")).toBe(true) + expect(await codeServer.isEditorVisible()).toBe(true) }) }) diff --git a/test/e2e/login.test.ts b/test/e2e/login.test.ts index e74c81c8..9e2f3da3 100644 --- a/test/e2e/login.test.ts +++ b/test/e2e/login.test.ts @@ -1,5 +1,6 @@ import { test, expect } from "@playwright/test" -import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants" +import { PASSWORD } from "../utils/constants" +import { CodeServer } from "./models/CodeServer" test.describe("login", () => { // Reset the browser so no cookies are persisted @@ -9,26 +10,32 @@ test.describe("login", () => { storageState: {}, }, } + let codeServer: CodeServer + + test.beforeEach(async ({ page }) => { + codeServer = new CodeServer(page) + await codeServer.navigate() + }) test("should see the login page", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) // It should send us to the login page expect(await page.title()).toBe("code-server login") }) test("should be able to login", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) // Type in password await page.fill(".password", PASSWORD) // Click the submit button and login await page.click(".submit") await 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.reloadUntilEditorIsVisible() // Make sure the editor actually loaded - expect(await page.isVisible("div.monaco-workbench")).toBe(true) + expect(await codeServer.isEditorVisible()).toBe(true) }) test("should see an error message for missing password", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) // Skip entering password // Click the submit button and login await page.click(".submit") @@ -37,7 +44,6 @@ test.describe("login", () => { }) test("should see an error message for incorrect password", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) // Type in password await page.fill(".password", "password123") // Click the submit button and login @@ -47,7 +53,6 @@ test.describe("login", () => { }) test("should hit the rate limiter for too many unsuccessful logins", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) // Type in password await page.fill(".password", "password123") // Click the submit button and login diff --git a/test/e2e/logout.test.ts b/test/e2e/logout.test.ts index aa080f5f..5e9dc8f9 100644 --- a/test/e2e/logout.test.ts +++ b/test/e2e/logout.test.ts @@ -1,5 +1,6 @@ import { test, expect } from "@playwright/test" import { CODE_SERVER_ADDRESS, PASSWORD } from "../utils/constants" +import { CodeServer } from "./models/CodeServer" test.describe("logout", () => { // Reset the browser so no cookies are persisted @@ -9,15 +10,24 @@ test.describe("logout", () => { storageState: {}, }, } + let codeServer: CodeServer + + test.beforeEach(async ({ page }) => { + codeServer = new CodeServer(page) + await codeServer.navigate() + }) + test("should be able login and logout", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) // Type in password await page.fill(".password", PASSWORD) // Click the submit button and login await page.click(".submit") await 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.reloadUntilEditorIsVisible() // Make sure the editor actually loaded - expect(await page.isVisible("div.monaco-workbench")).toBe(true) + expect(await codeServer.isEditorVisible()).toBe(true) // Click the Application menu await page.click("[aria-label='Application Menu']") diff --git a/test/e2e/models/CodeServer.ts b/test/e2e/models/CodeServer.ts index 8e2de316..d08e993b 100644 --- a/test/e2e/models/CodeServer.ts +++ b/test/e2e/models/CodeServer.ts @@ -15,7 +15,13 @@ export class CodeServer { */ async navigate() { await this.page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) + } + /** + * Checks if the editor is visible + * and reloads until it is + */ + async reloadUntilEditorIsVisible() { const editorIsVisible = await this.isEditorVisible() let reloadCount = 0 @@ -56,7 +62,12 @@ export class CodeServer { // then we can focus it by hitting the keyboard shortcut const isTerminalVisible = await this.page.isVisible("#terminal") if (isTerminalVisible) { - await this.page.keyboard.press(`Meta+Backquote`) + await this.page.keyboard.press(`Control+Backquote`) + // Wait for terminal to receive focus + await this.page.waitForSelector("div.terminal.xterm.focus") + // Sometimes the terminal reloads + // which is why we wait for it twice + await this.page.waitForSelector("div.terminal.xterm.focus") return } // Open using the manu @@ -70,5 +81,24 @@ export class CodeServer { // Click text=Terminal await this.page.hover("text=Terminal") await this.page.click("text=Terminal") + + // Wait for terminal to receive focus + // Sometimes the terminal reloads once or twice + // which is why we wait for it to have the focus class + await this.page.waitForSelector("div.terminal.xterm.focus") + // Sometimes the terminal reloads + // which is why we wait for it twice + await this.page.waitForSelector("div.terminal.xterm.focus") + } + + /** + * Navigates to CODE_SERVER_ADDRESS + * and reloads until the editor is visible + * + * Helpful for running before tests + */ + async setup() { + await this.navigate() + await this.reloadUntilEditorIsVisible() } } diff --git a/test/e2e/openHelpAbout.test.ts b/test/e2e/openHelpAbout.test.ts index 9de69695..a048ec38 100644 --- a/test/e2e/openHelpAbout.test.ts +++ b/test/e2e/openHelpAbout.test.ts @@ -1,10 +1,12 @@ import { test, expect } from "@playwright/test" -import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants" +import { STORAGE } from "../utils/constants" +import { CodeServer } from "./models/CodeServer" 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 @@ -15,32 +17,30 @@ test.describe("Open Help > About", () => { } } + test.beforeEach(async ({ page }) => { + codeServer = new CodeServer(page) + await codeServer.setup() + }) + test( "should see a 'Help' then 'About' button in the Application Menu that opens a dialog", options, async ({ page }) => { - await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) - // Make sure the editor actually loaded - expect(await page.isVisible("div.monaco-workbench")).toBe(true) + // Open using the manu + // Click [aria-label="Application Menu"] div[role="none"] + await page.click('[aria-label="Application Menu"] div[role="none"]') - // Click the Application menu - await page.click("[aria-label='Application Menu']") - // See the Help button - const helpButton = "a.action-menu-item span[aria-label='Help']" - expect(await page.isVisible(helpButton)).toBe(true) + // Click the Help button + await page.hover("text=Help") + await page.click("text=Help") - // Hover the helpButton - await page.hover(helpButton) + // Click the About button + await page.hover("text=About") + await page.click("text=About") - // see the About button and click it - const aboutButton = "a.action-menu-item span[aria-label='About']" - expect(await page.isVisible(aboutButton)).toBe(true) - // NOTE: it won't work unless you hover it first - await page.hover(aboutButton) - await page.click(aboutButton) - - const codeServerText = "text=code-server" - expect(await page.isVisible(codeServerText)).toBe(true) + // Click div[role="dialog"] >> text=code-server + const element = await page.waitForSelector('div[role="dialog"] >> text=code-server') + expect(element).not.toBeNull() }, ) }) diff --git a/test/e2e/terminal.test.ts b/test/e2e/terminal.test.ts index 0e1af1e4..53f4dcca 100644 --- a/test/e2e/terminal.test.ts +++ b/test/e2e/terminal.test.ts @@ -2,7 +2,8 @@ import { test, expect } from "@playwright/test" import * as fs from "fs" import { tmpdir } from "os" import * as path from "path" - +import util from "util" +import * as cp from "child_process" import { STORAGE } from "../utils/constants" import { CodeServer } from "./models/CodeServer" @@ -10,9 +11,11 @@ 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 = "test.txt" + const testFileName = "pipe" const testString = "new string test from e2e test" let codeServer: CodeServer + let tmpFolderPath: string = "" + let tmpFile: string = "" // TODO@jsjoeio // Fix this once https://github.com/microsoft/playwright-test/issues/240 @@ -25,26 +28,34 @@ test.describe("Integrated Terminal", () => { } test.beforeEach(async ({ page }) => { codeServer = new CodeServer(page) - await codeServer.navigate() - }) - - test("should echo a string to a file", options, async ({ page }) => { + await codeServer.setup() // NOTE@jsjoeio // We're not using tmpdir from src/node/constants // because Playwright doesn't fully support ES modules from // the erorrs I'm seeing - const tmpFolderPath = fs.mkdtempSync(path.join(tmpdir(), "code-server-test")) - const tmpFile = `${tmpFolderPath}${path.sep}${testFileName}` + tmpFolderPath = fs.mkdtempSync(path.join(tmpdir(), "code-server-test")) + tmpFile = path.join(tmpFolderPath, testFileName) + }) + + test.afterEach(async () => { + // Ensure directory was removed + fs.rmdirSync(tmpFolderPath, { recursive: true }) + }) + + test("should echo a string to a file", options, async ({ page }) => { + 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() - // give the terminal a second to load - await page.waitForTimeout(3000) - await page.keyboard.type(`echo '${testString}' > ${tmpFile}`) - // Wait for the typing to finish before hitting enter - await page.waitForTimeout(500) + await page.waitForLoadState("load") + await page.keyboard.type(`echo '${testString}' > '${tmpFile}'`) await page.keyboard.press("Enter") - await page.waitForTimeout(2000) + + const { stdout } = await output + expect(stdout).toMatch(testString) // .access checks if the file exists without opening it // it doesn't return anything hence why we expect it to