-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(testing): migrate to playwright-test from jest-playwright #3133
Changes from all commits
0a090bf
08cd2d8
5258670
7ea6d22
6c3bb10
92b7c1e
c9fa931
d6f0725
450fcd5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { | ||
ChromiumEnv, | ||
FirefoxEnv, | ||
WebKitEnv, | ||
test, | ||
setConfig, | ||
PlaywrightOptions, | ||
Config, | ||
globalSetup, | ||
} from "@playwright/test" | ||
import * as crypto from "crypto" | ||
import path from "path" | ||
import { PASSWORD } from "./utils/constants" | ||
import * as wtfnode from "./utils/wtfnode" | ||
|
||
// Playwright doesn't like that ../src/node/util has an enum in it | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👎 that you had to do this, but 👍 that you added a comment... I could certainly be better about doing this myself, it's a great habit 😄 |
||
// so I had to copy hash in separately | ||
const hash = (str: string): string => { | ||
return crypto.createHash("sha256").update(str).digest("hex") | ||
} | ||
|
||
const cookieToStore = { | ||
sameSite: "Lax" as const, | ||
name: "key", | ||
value: hash(PASSWORD), | ||
domain: "localhost", | ||
path: "/", | ||
expires: -1, | ||
httpOnly: false, | ||
secure: false, | ||
} | ||
|
||
globalSetup(async () => { | ||
console.log("\n🚨 Running globalSetup for playwright end-to-end tests") | ||
console.log("👋 Please hang tight...") | ||
|
||
if (process.env.WTF_NODE) { | ||
wtfnode.setup() | ||
} | ||
|
||
const storage = { | ||
cookies: [cookieToStore], | ||
} | ||
|
||
// Save storage state and store as an env variable | ||
// More info: https://playwright.dev/docs/auth?_highlight=authe#reuse-authentication-state | ||
process.env.STORAGE = JSON.stringify(storage) | ||
console.log("✅ globalSetup is now complete.") | ||
}) | ||
|
||
const config: Config = { | ||
testDir: path.join(__dirname, "e2e"), // Search for tests in this directory. | ||
timeout: 30000, // Each test is given 30 seconds. | ||
retries: 3, // Retry failing tests 2 times | ||
} | ||
|
||
if (process.env.CI) { | ||
// In CI, retry failing tests 2 times | ||
// in the event of flakiness | ||
config.retries = 2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this is to handle flakiness in CI environments? if so, a comment might be nice There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup! I thought the comment on L54 would suffice: But I'll add another There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that comment describes what the setting does but not why we need it, though :) IMO comments should be about the why because it gives context There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops...I guess I thought the flakiness covered the why but maybe not. Working on that 😅 |
||
} | ||
|
||
setConfig(config) | ||
|
||
const options: PlaywrightOptions = { | ||
headless: true, // Run tests in headless browsers. | ||
video: "retain-on-failure", | ||
} | ||
|
||
// Run tests in three browsers. | ||
test.runWith(new ChromiumEnv(options), { tag: "chromium" }) | ||
test.runWith(new FirefoxEnv(options), { tag: "firefox" }) | ||
test.runWith(new WebKitEnv(options), { tag: "webkit" }) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,15 @@ | ||
/// <reference types="jest-playwright-preset" /> | ||
|
||
// This test is for nothing more than to make sure | ||
// tests are running in multiple browsers | ||
describe("Browser gutcheck", () => { | ||
beforeEach(async () => { | ||
await jestPlaywright.resetBrowser({ | ||
logger: { | ||
isEnabled: (name) => name === "browser", | ||
log: (name, severity, message, args) => console.log(`${name} ${message}`), | ||
}, | ||
}) | ||
}) | ||
|
||
test("should display correct browser based on userAgent", async () => { | ||
const displayNames = { | ||
chromium: "Chrome", | ||
firefox: "Firefox", | ||
webkit: "Safari", | ||
} | ||
const userAgent = await page.evaluate("navigator.userAgent") | ||
|
||
if (browserName === "chromium") { | ||
expect(userAgent).toContain(displayNames[browserName]) | ||
} | ||
|
||
if (browserName === "firefox") { | ||
expect(userAgent).toContain(displayNames[browserName]) | ||
} | ||
|
||
if (browserName === "webkit") { | ||
expect(userAgent).toContain(displayNames[browserName]) | ||
} | ||
}) | ||
import { test, expect } from "@playwright/test" | ||
import { CODE_SERVER_ADDRESS } from "../utils/constants" | ||
|
||
// 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") | ||
|
||
expect(userAgent).toContain(displayNames[browserName]) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,46 @@ | ||
/// <reference types="jest-playwright-preset" /> | ||
import { test, expect } from "@playwright/test" | ||
import { CODE_SERVER_ADDRESS, STORAGE } from "../utils/constants" | ||
|
||
describe("Open Help > About", () => { | ||
beforeEach(async () => { | ||
// Create a new context with the saved storage state | ||
// so we don't have to logged in | ||
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 = {} | ||
// TODO@jsjoeio | ||
// Fix this once https://github.com/microsoft/playwright-test/issues/240 | ||
// is fixed | ||
if (STORAGE) { | ||
const storageState = JSON.parse(STORAGE) || {} | ||
await jestPlaywright.resetContext({ | ||
options.contextOptions = { | ||
storageState, | ||
}) | ||
await page.goto(CODE_SERVER_ADDRESS, { waitUntil: "networkidle" }) | ||
}) | ||
} | ||
} | ||
|
||
it("should see a 'Help' then 'About' button in the Application Menu that opens a dialog", async () => { | ||
// Make sure the editor actually loaded | ||
expect(await page.isVisible("div.monaco-workbench")) | ||
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")) | ||
|
||
// 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)) | ||
// Click the Application menu | ||
await page.click("[aria-label='Application Menu']") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do people still do page objects or is that not cool anymore? also, is it possible to select by IDs instead of other attributes, or is this the recommended approach with Playwright? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lol not sure about page objects. Unfortunately the way the HTML is structured, there isn't an ID to use as a selector. Otherwise, we would: <div class="menubar-menu-button" role="menuitem" tabindex="0" aria-label="Application Menu" title="Application Menu" aria-haspopup="true" style="visibility: visible;"><div class="menubar-menu-title toolbar-toggle-more codicon codicon-menubar-more" role="none" aria-hidden="true"></div></div> And this HTML comes from VS Code. We could add our own IDs, but there's a chance they get overwritten during a git subtree update to This means that if the VS Code Team changes the HTML (i.e. removes the aria-label) then our tests break. This can be annoying for us, but it's the best approach. Plus, aria-labels are less likely to change as frequently as something like a class so at least this attribute is a bit more stable. |
||
// See the Help button | ||
const helpButton = "a.action-menu-item span[aria-label='Help']" | ||
expect(await page.isVisible(helpButton)) | ||
|
||
// Hover the helpButton | ||
await page.hover(helpButton) | ||
// Hover the helpButton | ||
await page.hover(helpButton) | ||
|
||
// see the About button and click it | ||
const aboutButton = "a.action-menu-item span[aria-label='About']" | ||
expect(await page.isVisible(aboutButton)) | ||
// NOTE: it won't work unless you hover it first | ||
await page.hover(aboutButton) | ||
await page.click(aboutButton) | ||
// see the About button and click it | ||
const aboutButton = "a.action-menu-item span[aria-label='About']" | ||
expect(await page.isVisible(aboutButton)) | ||
// 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)) | ||
}) | ||
const codeServerText = "text=code-server" | ||
expect(await page.isVisible(codeServerText)) | ||
}, | ||
) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is just for testing, but would it be useful to randomize the password?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not. We need to password to be set as an environment variable for the tests in order to log in. I added a comment just now to explain that