mcpField
P
playwright-release-notes

playwright-release-notes

github.com/microsoftDevelopment98.8k14 installsUpdated today
Repository

About playwright-release-notes

playwright-release-notes is a skill for contributors preparing Playwright release documentation. It guides creation of Node.js notes and Python, Java, and .NET translations, then conversion of each section into language-specific GitHub release markdown.

Key features

  1. 01Draft Node.js release notes
  2. 02Translate notes for Python, Java, and .NET
  3. 03Apply language-specific API names and examples
  4. 04Convert notes to GitHub release markdown
  5. 05Check documentation links

Use cases

  1. 01Prepare release notes across four language docs
  2. 02Adapt API examples for each language
  3. 03Create and validate release announcement markdown

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/microsoft/playwright --skill playwright-release-notes

After installing, type/playwright-release-notesin Claude Code to confirm it loaded.

Details

From the project README

Release notes

In this skill, 1.xx is the release (for example 1.64).

Every release has a ## Version 1.xx section in four files in docs/src/:

LanguageNotesDocs base URLGitHub release in
Node.jsrelease-notes-js.mdhttps://playwright.dev/docs/microsoft/playwright
Pythonrelease-notes-python.mdhttps://playwright.dev/python/docs/microsoft/playwright-python
Javarelease-notes-java.mdhttps://playwright.dev/java/docs/microsoft/playwright-java
.NETrelease-notes-csharp.mdhttps://playwright.dev/dotnet/docs/microsoft/playwright-dotnet

Write the Node.js notes and the three translations in the same PR, so every language has its notes when it starts its release. The language repos then add only their own significant changes, and each one writes its GitHub release from its section (step 3).

1. Node.js notes

In docs/src/release-notes-js.md, add a ## Version 1.xx section above the previous version, and use previous versions as a guide:

  • Highlights come first, each with an emoji heading and a short sample. Ask teammates for help with them.
  • Then ### New APIs with #### groups, ### Breaking Changes ⚠️, ### Announcements and ### Browser Versions. Print the browser versions with node utils/print_versions.js.
  • Refer to APIs with docs link macros: [`method: Page.frameLocator`], [`property: Page.webmcp`], [`event: Page.dialogClosed`], [`option: Tracing.start.ariaSnapshots`], [Locator]. Each language's docs show them with that language's names.
  • Link other docs pages with relative paths: [test locks](./test-parallel.md#test-locks).

2. Python, Java and .NET translations

In each of release-notes-python.md, release-notes-java.md and release-notes-csharp.md, add a ## Version 1.xx section above the previous version. Translate it from the Node.js section. Compare the Version 1.63 sections of the files to see what past translations dropped and changed.

For every language:

  • Keep the structure: highlights with emoji headings, ### New APIs with #### groups, ### Breaking Changes ⚠️, ### Announcements and ### Browser Versions.
  • Remove what the language does not have:
    • the test runner: @playwright/test, test.*, config, fixtures, projects, reporters and npx playwright test options
    • UI mode, the HTML report, component testing and the VS Code extension
    • TypeScript-only features (generics, JSX, tsconfig) and devices descriptors
    • every API whose langs excludes the language (python, java, csharp). Check langs in docs/src/api/; options shared between methods are defined in docs/src/api/params.md.
  • Use the language's API where it differs from Node.js:
    • A language-specific option has its own langs. For example, recordVideoFps is langs: csharp, java, python, while Node.js has recordVideo.fps.
    • alias-<lang> renames a member, e.g. alias-java: isVisible for LocatorAssertions.toBeVisible.
    • The generated APIs in sibling checkouts confirm the names: ../playwright-python/playwright/sync_api/_generated.py, ../playwright-java/playwright/src/main/java/com/microsoft/playwright/ and ../playwright-dotnet/src/Playwright/API/Generated/.
  • When a Node.js sample shows a library feature through playwright.config.ts, show the API call instead. For example, the 1.64 video config became calls to Screencast.start and Screencast.showActions.
  • Keep API links as docs link macros, and link the language's own option where it differs ([`option: Browser.newContext.recordVideoFps`]). In plain text, use the language's names.
  • A relative link must point to a page that exists in the language: *-js.md pages are Node.js only. npm run doc reports broken links for each language.
  • Do not add changes that only a language port made. The language repo adds those to its notes.

Rewrite code samples in each language's style. The infra workflow checks the syntax of python, java and csharp snippets.

Python
  • Use the sync API: page.get_by_role("button").click(). Use snake_case names and Python values (True, False, None).
  • Options become keyword arguments: page.get_by_role("button", name="Save"). Objects and arrays become dicts and lists: size={"width": 1920, "height": 1080}.
  • Aria roles and other string values stay strings: "button", "ai".
  • Events: page.on("dialogclosed", handler). Assertions: expect(locator).to_have_text([...]). Print with print(...).
  • Use the alias-python names: route.continue_(), locator.or_(), page.expect_request().
  • Commands: write playwright install, not npx playwright install.
Java
Node.jsJava
await page.goto('...')page.navigate("...");: no await, double quotes, semicolons, typed locals (Locator dialog = ...)
page.getByRole('button', { name: 'Save' })page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Save"))
options object { path: 'video.webm', fps: 60 }options builder new Screencast.StartOptions().setPath(Paths.get("video.webm")).setFps(60)
object literal { name: 'session-id', value: '42', url }value class new Cookie("session-id", "42").setUrl(url)
['a', 'b'], { a: 2 }Arrays.asList("a", "b"), Map.of("a", 2); new String[] {"a", "b"} for assertion arguments
property page.webmcp, context.tracinggetter page.webmcp(), context.tracing()
event page.on('dialogclosed', handler)page.onDialogClosed(handler)
expect(locator).toHaveText(...)assertThat(locator).hasText(...). Likewise toBeX → isX, toHaveX → hasX, toContainX → containsX, toMatchX → matchesX
route.continue() and other members with alias-javathe alias, e.g. route.resume()
npx playwright install --no-removemvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --no-remove"
"array", undefined, 'ai' in prose"list", null, "ai"
.NET
Node.js.NET
await page.goto('...')await page.GotoAsync("...");: PascalCase members, Async suffix on async methods, var locals, double quotes, semicolons
page.getByRole('button', { name: 'Save' })page.GetByRole(AriaRole.Button, new() { Name = "Save" })
options object { path: 'video.webm', fps: 60 }options initializer new() { Path = "video.webm", Fps = 60 }
nested object size: { width: 1920, height: 1080 }Size = new() { Width = 1920, Height = 1080 }
object literal { name: 'session-id', value: '42', url }new Cookie { Name = "session-id", Value = "42", Url = url }
['a', 'b'], free-form object { a: 2 }new[] { "a", "b" }, anonymous object new { a = 2 }
string-literal union value 'ai', 'button'enum member AriaSnapshotMode.Ai, AriaRole.Button
property page.webmcp, context.tracingproperty page.Webmcp, context.Tracing
event page.on('dialogclosed', handler)page.DialogClosed += (_, dialog) => { ... };
expect(locator).toHaveText(...)await Expect(locator).ToHaveTextAsync(...);
console.log(x)Console.WriteLine(x);
members with alias-csharpthe alias, e.g. page.request → page.APIRequest
union-typed option, e.g. httpCredentials as a listone property per type, e.g. HttpCredentialsList
npx playwright install --no-removepwsh bin/Debug/netX/playwright.ps1 install --no-remove
option names in prose (fontSize), undefinedFontSize, null

Open one PR for all four files, titled docs: release notes for v1.xx. After it merges, cherry-pick it to release-1.xx with the playwright-cherry-pick skill.

3. GitHub release markdown

Each repo publishes the GitHub release for its language, with markdown converted from its ## Version 1.xx section on release-1.xx. Convert the notes yourself:

  • Leave out the ## Version 1.xx heading. Move every other heading up one level (### becomes ##, #### becomes ###).

  • Replace each link macro with a full link: the language's docs base URL from the table at the top, plus the path below. Use the language's name as the link text:

    MacroPath after the docs base URLNode.jsPythonJava.NET
    [Locator]api/class-locatorLocatorLocatorLocatorLocator
    [`method: Page.frameLocator`]api/class-page#page-frame-locatorpage.frameLocator()page.frame_locator()page.frameLocator()Page.FrameLocator()
    [`method: Page.ariaSnapshot`]api/class-page#page-aria-snapshotpage.ariaSnapshot()page.aria_snapshot()page.ariaSnapshot()Page.AriaSnapshotAsync()
    [`method: Locator.first`]api/class-locator#locator-firstlocator.first()locator.firstlocator.first()Locator.First
    [`property: Page.webmcp`]api/class-page#page-webmcppage.webmcppage.webmcppage.webmcp()Page.Webmcp
    [`event: BrowserContext.dialogClosed`]api/class-browsercontext#browser-context-event-dialog-closedbrowserContext.on('dialogclosed')browser_context.on("dialogclosed")browserContext.onDialogClosed()BrowserContext.DialogClosed
    [`method: LocatorAssertions.toBeVisible`]api/class-locatorassertions#locator-assertions-to-be-visibleexpect(locator).toBeVisible()expect(locator).to_be_visible()assertThat(locator).isVisible()Expect(Locator).ToBeVisibleAsync()
    [`option: Page.content.includeShadow`]api/class-page#page-content-option-include-shadow`includeShadow``include_shadow``includeShadow``IncludeShadow`
    [Playwright MCP](./getting-started-mcp.md)getting-started-mcpPlaywright MCPPlaywright MCPPlaywright MCPPlaywright MCP
  • An image ![alt](./images/foo.png) becomes ![alt](https://github.com/microsoft/playwright/blob/main/docs/src/images/foo.png?raw=true) in every language.

The page is class- and the class name in lower case. The anchor is the class and member names in kebab-case, so APIRequestContext.addCookies becomes api-request-context-add-cookies. Options add -option-<name>, and events add -event-<name>. Anchors use the names from docs/src/api/ even where a language renames the member, so Java's isVisible() links to #locator-assertions-to-be-visible.

The link text follows the language:

  • Node.js: the class in lower camel case (page, apiRequestContext, testConfig). Methods get (), properties none, events become on('<event in lower case>'), and assertions start with expect(locator)., expect(page). or expect(response)..
  • Python: the class and member in snake_case (browser_context, api_request_context). Methods get (). A member is a property, without (), if _generated.py declares it with @property. Events become on("<event in lower case>"), and assertions start with expect(locator)..
  • Java: the class in lower camel case and the alias-java member name. Methods and properties get (), events become on<Event>(), and assertions start with assertThat(locator)..
  • .NET: the class without the interface I (Page, APIRequestContext) and the member exactly as the generated I<Class>.cs declares it. Async methods take the Async suffix and (), sync methods get (), and properties and events get no parentheses. Assertions start with Expect(Locator)..

Check that every link resolves. playwright.dev already documents the release once the Node.js docs roll has landed. It serves minified HTML, so an anchor may appear without quotes (id=page-webmcp>):

grep -oE 'https://playwright\.dev/[^) ]+' <notes-file> | sort -u | while read -r url; do
  page=${url%%#*}
  html=$(curl -sfL "$page") || { echo "MISSING PAGE   $url"; continue; }
  if [[ "$url" == *"#"* ]]; then
    anchor=${url#*#}
    grep -qE "id=\"?${anchor}[\" >]" <<<"$html" || echo "MISSING ANCHOR $url"
  fi
done

Then scan for anything that wasn't converted, and for stray spaces inside link text ([Page. DialogClosed], [`HttpCredentialsList `]). The first command applies to every language; then run the one for the language. They should print nothing:

# every language
grep -nE '\[`(method|property|event|option|param): |\]\(\./|\[[A-Z][A-Za-z]*\]([^(]|$)|\[[A-Za-z_]+\. |\[`[^`]+ `\]' <notes-file>
# Python: camelCase names in link text
grep -nE '\[[a-z_]+\.[a-z_]*[A-Z]|\[`[a-z]+[A-Z]' <notes-file>
# Java: JS-style events, assertions and properties
grep -nE "\.on\('|\b(toBe|toHave|toContain|toMatch)[A-Z]|\bawait\b|\barray\b|\bundefined\b|\[[a-z][A-Za-z]*\.[a-z][A-Za-z]*\]\(" <notes-file>
# .NET: lower-case classes or members, JS-style events
grep -nE "\.on\('|\bundefined\b|\[[a-z][A-Za-z]*\.|\[[A-Z][A-Za-z]*\.[a-z]|\[Expect\([a-z]" <notes-file>

If the conversion shows a problem in the notes themselves, such as an API the language does not have or a sample in the wrong style, fix the notes too (step 2). Otherwise the problem stays on playwright.dev.

Comments

Markdown supported