diff --git a/.github/workflows/infra.yml b/.github/workflows/infra.yml index f33c8535f07f1..905597c8bddd0 100644 --- a/.github/workflows/infra.yml +++ b/.github/workflows/infra.yml @@ -38,7 +38,7 @@ jobs: run: npm audit --omit dev lint-snippets: name: "Lint snippets" - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -50,6 +50,12 @@ jobs: - uses: actions/setup-dotnet@v4 with: dotnet-version: 8.0.x + - uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '21' - run: npm ci - run: pip install -r utils/doclint/linting-code-snippets/python/requirements.txt + - run: mvn package + working-directory: utils/doclint/linting-code-snippets/java - run: node utils/doclint/linting-code-snippets/cli.js diff --git a/docs/src/accessibility-testing-java.md b/docs/src/accessibility-testing-java.md index d62bac64fad37..13f9c4e3e496c 100644 --- a/docs/src/accessibility-testing-java.md +++ b/docs/src/accessibility-testing-java.md @@ -70,22 +70,24 @@ For example, you can use [`AxeBuilder.include()`](https://github.com/dequelabs/a `AxeBuilder.analyze()` will scan the page *in its current state* when you call it. To scan parts of a page that are revealed based on UI interactions, use [Locators](./locators.md) to interact with the page before invoking `analyze()`: ```java -@Test -void navigationMenuFlyoutShouldNotHaveAutomaticallyDetectableAccessibilityViolations() throws Exception { - page.navigate("https://your-site.com/"); +public class HomepageTests { + @Test + void navigationMenuFlyoutShouldNotHaveAutomaticallyDetectableAccessibilityViolations() throws Exception { + page.navigate("https://your-site.com/"); - page.locator("button[aria-label=\"Navigation Menu\"]").click(); + page.locator("button[aria-label=\"Navigation Menu\"]").click(); - // It is important to waitFor() the page to be in the desired - // state *before* running analyze(). Otherwise, axe might not - // find all the elements your test expects it to scan. - page.locator("#navigation-menu-flyout").waitFor(); + // It is important to waitFor() the page to be in the desired + // state *before* running analyze(). Otherwise, axe might not + // find all the elements your test expects it to scan. + page.locator("#navigation-menu-flyout").waitFor(); - AxeResults accessibilityScanResults = new AxeBuilder(page) - .include(Arrays.asList("#navigation-menu-flyout")) - .analyze(); + AxeResults accessibilityScanResults = new AxeBuilder(page) + .include(Arrays.asList("#navigation-menu-flyout")) + .analyze(); - assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations()); + assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations()); + } } ``` @@ -158,38 +160,40 @@ This approach avoids the downsides of using `AxeBuilder.exclude()` at the cost o Here is an example of using fingerprints based on only rule IDs and "target" selectors pointing to each violation: ```java -@Test -shouldOnlyHaveAccessibilityViolationsMatchingKnownFingerprints() throws Exception { - page.navigate("https://your-site.com/"); +public class HomepageTests { + @Test + shouldOnlyHaveAccessibilityViolationsMatchingKnownFingerprints() throws Exception { + page.navigate("https://your-site.com/"); - AxeResults accessibilityScanResults = new AxeBuilder(page).analyze(); + AxeResults accessibilityScanResults = new AxeBuilder(page).analyze(); - List violationFingerprints = fingerprintsFromScanResults(accessibilityScanResults); + List violationFingerprints = fingerprintsFromScanResults(accessibilityScanResults); - assertEquals(Arrays.asList( - new ViolationFingerprint("aria-roles", "[span[role=\"invalid\"]]"), - new ViolationFingerprint("color-contrast", "[li:nth-child(2) > span]"), - new ViolationFingerprint("label", "[input]") - ), violationFingerprints); -} + assertEquals(Arrays.asList( + new ViolationFingerprint("aria-roles", "[span[role=\"invalid\"]]"), + new ViolationFingerprint("color-contrast", "[li:nth-child(2) > span]"), + new ViolationFingerprint("label", "[input]") + ), violationFingerprints); + } -// You can make your "fingerprint" as specific as you like. This one considers a violation to be -// "the same" if it corresponds the same Axe rule on the same element. -// -// Using a record type makes it easy to compare fingerprints with assertEquals -public record ViolationFingerprint(String ruleId, String target) { } - -public List fingerprintsFromScanResults(AxeResults results) { - return results.getViolations().stream() - // Each violation refers to one rule and multiple "nodes" which violate it - .flatMap(violation -> violation.getNodes().stream() - .map(node -> new ViolationFingerprint( - violation.getId(), - // Each node contains a "target", which is a CSS selector that uniquely identifies it - // If the page involves iframes or shadow DOMs, it may be a chain of CSS selectors - node.getTarget().toString() - ))) - .collect(Collectors.toList()); + // You can make your "fingerprint" as specific as you like. This one considers a violation to be + // "the same" if it corresponds the same Axe rule on the same element. + // + // Using a record type makes it easy to compare fingerprints with assertEquals + public record ViolationFingerprint(String ruleId, String target) { } + + public List fingerprintsFromScanResults(AxeResults results) { + return results.getViolations().stream() + // Each violation refers to one rule and multiple "nodes" which violate it + .flatMap(violation -> violation.getNodes().stream() + .map(node -> new ViolationFingerprint( + violation.getId(), + // Each node contains a "target", which is a CSS selector that uniquely identifies it + // If the page involves iframes or shadow DOMs, it may be a chain of CSS selectors + node.getTarget().toString() + ))) + .collect(Collectors.toList()); + } } ``` @@ -208,11 +212,11 @@ This example fixture creates an `AxeBuilder` object which is pre-configured with ```java class AxeTestFixtures extends TestFixtures { - AxeBuilder makeAxeBuilder() { - return new AxeBuilder(page) - .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) - .exclude('#commonly-reused-element-with-known-issue'); - } + AxeBuilder makeAxeBuilder() { + return new AxeBuilder(page) + .withTags(new String[]{"wcag2a", "wcag2aa", "wcag21a", "wcag21aa"}) + .exclude("#commonly-reused-element-with-known-issue"); + } } ``` @@ -229,7 +233,7 @@ public class HomepageTests extends AxeTestFixtures { AxeResults accessibilityScanResults = makeAxeBuilder() // Automatically uses the shared AxeBuilder configuration, // but supports additional test-specific configuration too - .include('#specific-element-under-test') + .include("#specific-element-under-test") .analyze(); assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations()); diff --git a/docs/src/api-testing-java.md b/docs/src/api-testing-java.md index 987aad5de87a0..e8020e12ce4f3 100644 --- a/docs/src/api-testing-java.md +++ b/docs/src/api-testing-java.md @@ -194,6 +194,7 @@ public class TestGitHubAPI { These tests assume that repository exists. You probably want to create a new one before running tests and delete it afterwards. Use `@BeforeAll` and `@AfterAll` hooks for that. ```java +public class TestGitHubAPI { // ... void createTestRepository() { @@ -223,6 +224,7 @@ These tests assume that repository exists. You probably want to create a new one disposeAPIRequestContext(); closePlaywright(); } +} ``` ### Complete test example @@ -381,18 +383,20 @@ The following test creates a new issue via API and then navigates to the list of project to check that it appears at the top of the list. The check is performed using [LocatorAssertions]. ```java -@Test -void lastCreatedIssueShouldBeFirstInTheList() { - Map data = new HashMap<>(); - data.put("title", "[Feature] request 1"); - data.put("body", "Feature description"); - APIResponse newIssue = request.post("/repos/" + USER + "/" + REPO + "/issues", - RequestOptions.create().setData(data)); - assertTrue(newIssue.ok()); - - page.navigate("https://github.com/" + USER + "/" + REPO + "/issues"); - Locator firstIssue = page.locator("a[data-hovercard-type='issue']").first(); - assertThat(firstIssue).hasText("[Feature] request 1"); +public class TestGitHubAPI { + @Test + void lastCreatedIssueShouldBeFirstInTheList() { + Map data = new HashMap<>(); + data.put("title", "[Feature] request 1"); + data.put("body", "Feature description"); + APIResponse newIssue = request.post("/repos/" + USER + "/" + REPO + "/issues", + RequestOptions.create().setData(data)); + assertTrue(newIssue.ok()); + + page.navigate("https://github.com/" + USER + "/" + REPO + "/issues"); + Locator firstIssue = page.locator("a[data-hovercard-type='issue']").first(); + assertThat(firstIssue).hasText("[Feature] request 1"); + } } ``` @@ -402,18 +406,20 @@ The following test creates a new issue via user interface in the browser and the it was created: ```java -@Test -void lastCreatedIssueShouldBeOnTheServer() { - page.navigate("https://github.com/" + USER + "/" + REPO + "/issues"); - page.locator("text=New Issue").click(); - page.locator("[aria-label='Title']").fill("Bug report 1"); - page.locator("[aria-label='Comment body']").fill("Bug description"); - page.locator("text=Submit new issue").click(); - String issueId = page.url().substring(page.url().lastIndexOf('/')); - - APIResponse newIssue = request.get("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId); - assertThat(newIssue).isOK(); - assertTrue(newIssue.text().contains("Bug report 1")); +public class TestGitHubAPI { + @Test + void lastCreatedIssueShouldBeOnTheServer() { + page.navigate("https://github.com/" + USER + "/" + REPO + "/issues"); + page.locator("text=New Issue").click(); + page.locator("[aria-label='Title']").fill("Bug report 1"); + page.locator("[aria-label='Comment body']").fill("Bug description"); + page.locator("text=Submit new issue").click(); + String issueId = page.url().substring(page.url().lastIndexOf('/')); + + APIResponse newIssue = request.get("https://github.com/" + USER + "/" + REPO + "/issues/" + issueId); + assertThat(newIssue).isOK(); + assertTrue(newIssue.text().contains("Bug report 1")); + } } ``` diff --git a/docs/src/api/class-apiresponseassertions.md b/docs/src/api/class-apiresponseassertions.md index 6fce864d0323d..9584365d887dd 100644 --- a/docs/src/api/class-apiresponseassertions.md +++ b/docs/src/api/class-apiresponseassertions.md @@ -14,15 +14,15 @@ test('navigates to login', async ({ page }) => { ``` ```java -... +// ... import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; public class TestPage { - ... + // ... @Test void navigatesToLoginPage() { - ... - APIResponse response = page.request().get('https://playwright.dev'); + // ... + APIResponse response = page.request().get("https://playwright.dev"); assertThat(response).isOK(); } } diff --git a/docs/src/api/class-browser.md b/docs/src/api/class-browser.md index 59cf4c99c0aac..4dabfc52e4246 100644 --- a/docs/src/api/class-browser.md +++ b/docs/src/api/class-browser.md @@ -18,15 +18,15 @@ const { firefox } = require('playwright'); // Or 'chromium' or 'webkit'. import com.microsoft.playwright.*; public class Example { - public static void main(String[] args) { - try (Playwright playwright = Playwright.create()) { - BrowserType firefox = playwright.firefox() - Browser browser = firefox.launch(); - Page page = browser.newPage(); - page.navigate('https://example.com'); - browser.close(); - } - } + public static void main(String[] args) { + try (Playwright playwright = Playwright.create()) { + BrowserType firefox = playwright.firefox(); + Browser browser = firefox.launch(); + Page page = browser.newPage(); + page.navigate("https://example.com"); + browser.close(); + } + } } ``` @@ -202,7 +202,7 @@ Browser browser = playwright.firefox().launch(); // Or 'chromium' or 'webkit'. BrowserContext context = browser.newContext(); // Create a new page in a pristine context. Page page = context.newPage(); -page.navigate('https://example.com'); +page.navigate("https://example.com"); // Graceful close up everything context.close(); @@ -331,7 +331,7 @@ await browser.stopTracing(); ```java browser.startTracing(page, new Browser.StartTracingOptions() .setPath(Paths.get("trace.json"))); -page.goto('https://www.google.com'); +page.navigate("https://www.google.com"); browser.stopTracing(); ``` diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index b504bf457b16a..8d6c57a3e3c8a 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -655,7 +655,7 @@ import com.microsoft.playwright.*; public class Example { public static void main(String[] args) { try (Playwright playwright = Playwright.create()) { - BrowserType webkit = playwright.webkit() + BrowserType webkit = playwright.webkit(); Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); BrowserContext context = browser.newContext(); context.exposeBinding("pageURL", (source, args) -> source.page().url()); @@ -813,8 +813,9 @@ import java.util.Base64; public class Example { public static void main(String[] args) { try (Playwright playwright = Playwright.create()) { - BrowserType webkit = playwright.webkit() + BrowserType webkit = playwright.webkit(); Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); + BrowserContext context = browser.newContext(); context.exposeFunction("sha256", args -> { String text = (String) args[0]; MessageDigest crypto; diff --git a/docs/src/api/class-consolemessage.md b/docs/src/api/class-consolemessage.md index 2268ee3ed10a8..347838ae42141 100644 --- a/docs/src/api/class-consolemessage.md +++ b/docs/src/api/class-consolemessage.md @@ -44,8 +44,8 @@ ConsoleMessage msg = page.waitForConsoleMessage(() -> { }); // Deconstruct console.log arguments -msg.args().get(0).jsonValue() // hello -msg.args().get(1).jsonValue() // 42 +msg.args().get(0).jsonValue(); // hello +msg.args().get(1).jsonValue(); // 42 ``` ```python async diff --git a/docs/src/api/class-formdata.md b/docs/src/api/class-formdata.md index 5ca85b361d00e..c5f4bcb51d14f 100644 --- a/docs/src/api/class-formdata.md +++ b/docs/src/api/class-formdata.md @@ -6,7 +6,7 @@ The [FormData] is used create form data that is sent via [APIRequestContext]. ```java import com.microsoft.playwright.options.FormData; -... +// ... FormData form = FormData.create() .set("firstName", "John") .set("lastName", "Doe") @@ -28,7 +28,7 @@ the new value onto the end of the existing set of values. ```java import com.microsoft.playwright.options.FormData; -... +// ... FormData form = FormData.create() // Only name and value are set. .append("firstName", "John") @@ -100,7 +100,7 @@ Sets a field on the form. File values can be passed either as `Path` or as `File ```java import com.microsoft.playwright.options.FormData; -... +// ... FormData form = FormData.create() // Only name and value are set. .set("firstName", "John") diff --git a/docs/src/api/class-keyboard.md b/docs/src/api/class-keyboard.md index 64a539cfbe9f9..f86c4ad11f4b6 100644 --- a/docs/src/api/class-keyboard.md +++ b/docs/src/api/class-keyboard.md @@ -257,7 +257,7 @@ await browser.close(); Page page = browser.newPage(); page.navigate("https://keycode.info"); page.keyboard().press("A"); -page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")); +page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"))); page.keyboard().press("ArrowLeft"); page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png"))); page.keyboard().press("Shift+O"); diff --git a/docs/src/api/class-locator.md b/docs/src/api/class-locator.md index 3a9504ad59a4a..8f0b53e16da7f 100644 --- a/docs/src/api/class-locator.md +++ b/docs/src/api/class-locator.md @@ -38,7 +38,7 @@ for li in page.get_by_role('listitem').all(): ``` ```java -for (Locator li : page.getByRole('listitem').all()) +for (Locator li : page.getByRole("listitem").all()) li.click(); ``` diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 1ff5e52119b37..bfff2b07adb03 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -14,14 +14,14 @@ test('status becomes submitted', async ({ page }) => { ``` ```java -... +// ... import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; public class TestLocator { - ... + // ... @Test void statusBecomesSubmitted() { - ... + // ... page.getByRole(AriaRole.BUTTON).click(); assertThat(page.locator(".status")).hasText("Submitted"); } @@ -2048,7 +2048,7 @@ await expect(locator).toHaveValues([/R/, /G/]); ``` ```java -page.locator("id=favorite-colors").selectOption(["R", "G"]); +page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"}); assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") }); ``` diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 60512f51ca70d..7a3be809c6ba5 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -1041,9 +1041,9 @@ await page.dragAndDrop('#source', '#target', { ``` ```java -page.dragAndDrop("#source", '#target'); +page.dragAndDrop("#source", "#target"); // or specify exact positions relative to the top-left corners of the elements: -page.dragAndDrop("#source", '#target', new Page.DragAndDropOptions() +page.dragAndDrop("#source", "#target", new Page.DragAndDropOptions() .setSourcePosition(34, 7).setTargetPosition(10, 20)); ``` @@ -1716,7 +1716,7 @@ public class Example { public static void main(String[] args) { try (Playwright playwright = Playwright.create()) { BrowserType webkit = playwright.webkit(); - Browser browser = webkit.launch({ headless: false }); + Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); BrowserContext context = browser.newContext(); Page page = context.newPage(); page.exposeBinding("pageURL", (source, args) -> source.page().url()); @@ -1886,26 +1886,27 @@ public class Example { public static void main(String[] args) { try (Playwright playwright = Playwright.create()) { BrowserType webkit = playwright.webkit(); - Browser browser = webkit.launch({ headless: false }); + Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false)); Page page = browser.newPage(); page.exposeFunction("sha256", args -> { - String text = (String) args[0]; - MessageDigest crypto; try { - crypto = MessageDigest.getInstance("SHA-256"); + String text = (String) args[0]; + MessageDigest crypto = MessageDigest.getInstance("SHA-256"); + byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(token); } catch (NoSuchAlgorithmException e) { return null; } - byte[] token = crypto.digest(text.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(token); }); - page.setContent("\n" + "\n" + - "
\n"); + "
" + ); page.click("button"); } } @@ -2106,7 +2107,7 @@ const frame = page.frame({ url: /.*domain.*/ }); ``` ```java -Frame frame = page.frameByUrl(Pattern.compile(".*domain.*"); +Frame frame = page.frameByUrl(Pattern.compile(".*domain.*")); ``` ```py @@ -3161,12 +3162,12 @@ await page.getByRole('button', { name: 'Start here' }).click(); ```java // Setup the handler. -page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () => { +page.addLocatorHandler(page.getByText("Sign up to the newsletter"), () -> { page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("No thanks")).click(); }); // Write the test as usual. -page.goto("https://example.com"); +page.navigate("https://example.com"); page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); ``` @@ -3218,12 +3219,12 @@ await page.getByRole('button', { name: 'Start here' }).click(); ```java // Setup the handler. -page.addLocatorHandler(page.getByText("Confirm your security details")), () => { +page.addLocatorHandler(page.getByText("Confirm your security details"), () -> { page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click(); }); // Write the test as usual. -page.goto("https://example.com"); +page.navigate("https://example.com"); page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); ``` @@ -3275,12 +3276,12 @@ await page.getByRole('button', { name: 'Start here' }).click(); ```java // Setup the handler. -page.addLocatorHandler(page.locator("body")), () => { +page.addLocatorHandler(page.locator("body"), () -> { page.evaluate("window.removeObstructionsForTestIfNeeded()"); -}, new Page.AddLocatorHandlerOptions.setNoWaitAfter(true)); +}, new Page.AddLocatorHandlerOptions().setNoWaitAfter(true)); // Write the test as usual. -page.goto("https://example.com"); +page.navigate("https://example.com"); page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click(); ``` @@ -3326,7 +3327,7 @@ await page.addLocatorHandler(page.getByLabel('Close'), async locator => { ``` ```java -page.addLocatorHandler(page.getByLabel("Close"), locator => { +page.addLocatorHandler(page.getByLabel("Close"), locator -> { locator.click(); }, new Page.AddLocatorHandlerOptions().setTimes(1)); ``` diff --git a/docs/src/api/class-pageassertions.md b/docs/src/api/class-pageassertions.md index 5e56907656d32..8eefd41f023aa 100644 --- a/docs/src/api/class-pageassertions.md +++ b/docs/src/api/class-pageassertions.md @@ -14,14 +14,14 @@ test('navigates to login', async ({ page }) => { ``` ```java -... +// ... import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; public class TestPage { - ... + // ... @Test void navigatesToLoginPage() { - ... + // ... page.getByText("Sign in").click(); assertThat(page).hasURL(Pattern.compile(".*/login")); } diff --git a/docs/src/api/class-playwrightassertions.md b/docs/src/api/class-playwrightassertions.md index 7a960e34e1a56..aa4e0a42a4bf4 100644 --- a/docs/src/api/class-playwrightassertions.md +++ b/docs/src/api/class-playwrightassertions.md @@ -35,14 +35,13 @@ def test_status_becomes_submitted(page: Page) -> None: ``` ```java -... import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; public class TestExample { - ... + // ... @Test void statusBecomesSubmitted() { - ... + // ... page.locator("#submit-button").click(); assertThat(page.locator(".status")).hasText("Submitted"); } diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 1f9c03d77af35..462b225e6e758 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1488,19 +1488,19 @@ page.get_by_text(re.compile("^hello$", re.IGNORECASE)) ```java // Matches -page.getByText("world") +page.getByText("world"); // Matches first
-page.getByText("Hello world") +page.getByText("Hello world"); // Matches second
-page.getByText("Hello", new Page.GetByTextOptions().setExact(true)) +page.getByText("Hello", new Page.GetByTextOptions().setExact(true)); // Matches both
s -page.getByText(Pattern.compile("Hello")) +page.getByText(Pattern.compile("Hello")); // Matches second
-page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)) +page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE)); ``` ```csharp diff --git a/docs/src/emulation.md b/docs/src/emulation.md index d1f7bc3400186..12b44a49c8e2e 100644 --- a/docs/src/emulation.md +++ b/docs/src/emulation.md @@ -188,7 +188,7 @@ page.setViewportSize(1600, 1200); // Emulate high-DPI BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setViewportSize(2560, 1440) - .setDeviceScaleFactor(2); + .setDeviceScaleFactor(2)); ``` ```python async @@ -378,7 +378,7 @@ const context = await browser.newContext({ ```java BrowserContext context = browser.newContext(new Browser.NewContextOptions() - .setPermissions(Arrays.asList("notifications")); + .setPermissions(Arrays.asList("notifications"))); ``` ```python async diff --git a/docs/src/locators.md b/docs/src/locators.md index 8ade71efb7faf..0aa918e53c998 100644 --- a/docs/src/locators.md +++ b/docs/src/locators.md @@ -122,7 +122,7 @@ await locator.click(); ```java Locator locator = page.getByRole(AriaRole.BUTTON, - new Page.GetByRoleOptions().setName("Sign in")) + new Page.GetByRoleOptions().setName("Sign in")); locator.hover(); locator.click(); @@ -946,7 +946,7 @@ page.getByRole(AriaRole.LISTITEM) .setName("Product 2")))) .getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Add to cart")) - .click() + .click(); ``` ```python async @@ -987,7 +987,7 @@ assertThat(page .getByRole(AriaRole.LISTITEM) .filter(new Locator.FilterOptions() .setHas(page.GetByRole(AriaRole.HEADING, - new Page.GetByRoleOptions().setName("Product 2")))) + new Page.GetByRoleOptions().setName("Product 2"))))) .hasCount(1); ``` @@ -1033,7 +1033,7 @@ assertThat(page .filter(new Locator.FilterOptions() .setHas(page.GetByRole(AriaRole.LIST) .GetByRole(AriaRole.HEADING, - new Page.GetByRoleOptions().setName("Product 2")))) + new Page.GetByRoleOptions().setName("Product 2"))))) .hasCount(1); ``` @@ -1079,7 +1079,7 @@ await expect(page ```java assertThat(page .getByRole(AriaRole.LISTITEM) - .filter(new Locator.FilterOptions().setHasNot(page.getByText("Product 2"))) + .filter(new Locator.FilterOptions().setHasNot(page.getByText("Product 2")))) .hasCount(1); ``` @@ -1356,7 +1356,7 @@ expect(page.get_by_role("listitem")).to_have_count(3) ``` ```java -assertThat(page.getByRole(AriaRole.LISTITEM).hasCount(3); +assertThat(page.getByRole(AriaRole.LISTITEM)).hasCount(3); ``` ```csharp diff --git a/docs/src/mock.md b/docs/src/mock.md index 87ddf2ec96c3a..5c87e91d5b15e 100644 --- a/docs/src/mock.md +++ b/docs/src/mock.md @@ -195,15 +195,15 @@ await Expect(page.GetByTextAsync("Loquat", new () { Exact = true })).ToBeVisible page.route("*/**/api/v1/fruits", route -> { Response response = route.fetch(); byte[] json = response.body(); - parsed = new Gson().fromJson(json, JsonObject.class) + JsonObject parsed = new Gson().fromJson(new String(json), JsonObject.class); parsed.add(new JsonObject().add("name", "Loquat").add("id", 100)); // Fulfill using the original response, while patching the response body // with the given JSON object. - route.fulfill(new Route.FulfillOptions().setResponse(response).setBody(json.toString())); + route.fulfill(new Route.FulfillOptions().setResponse(response).setBody(parsed.toString())); }); // Go to the page -page.goto("https://demo.playwright.dev/api-mocking"); +page.navigate("https://demo.playwright.dev/api-mocking"); // Assert that the Loquat fruit is visible assertThat(page.getByText("Loquat", new Page.GetByTextOptions().setExact(true))).isVisible(); @@ -294,7 +294,7 @@ page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions() ); // Go to the page -page.goto("https://demo.playwright.dev/api-mocking"); +page.navigate("https://demo.playwright.dev/api-mocking"); // Assert that the fruit is visible assertThat(page.getByText("Strawberry")).isVisible(); @@ -392,10 +392,11 @@ page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions() ); // Go to the page -page.goto("https://demo.playwright.dev/api-mocking"); +page.navigate("https://demo.playwright.dev/api-mocking"); // Assert that the Playwright fruit is visible -assertThat(page.getByText("Playwright", new Page.GetByTextOptions().setExact(true))).isVisible(); +assertThat(page.getByText("Playwright", new Page.GetByTextOptions() + .setExact(true))).isVisible(); ``` In the trace of our test we can see that the route was fulfilled from the HAR file and the API was not called. ![trace showing the HAR file being used](https://github.com/microsoft/playwright/assets/13063165/1bd7ab66-ea4f-43c2-a4e5-ca17d4837ff1) diff --git a/docs/src/network.md b/docs/src/network.md index 4d6f229678b84..33c012d9b5d11 100644 --- a/docs/src/network.md +++ b/docs/src/network.md @@ -146,8 +146,8 @@ const browser = await chromium.launch({ ```java Browser browser = chromium.launch(new BrowserType.LaunchOptions() .setProxy(new Proxy("http://myproxy.com:3128") - .setUsername('usr') - .setPassword('pwd'))); + .setUsername("usr") + .setPassword("pwd"))); ``` ```python async @@ -627,7 +627,7 @@ page.route("**/title.html", route -> { String body = response.text(); body = body.replace("", "<title>My prefix:"); Map<String, String> headers = response.headers(); - headers.put("content-type": "text/html"); + headers.put("content-type", "text/html"); route.fulfill(new Route.FulfillOptions() // Pass all fields from the response. .setResponse(response) diff --git a/docs/src/pom.md b/docs/src/pom.md index 983598a2c2825..5108d5718ba95 100644 --- a/docs/src/pom.md +++ b/docs/src/pom.md @@ -289,7 +289,7 @@ Page objects can then be used inside a test. ```java import models.SearchPage; import com.microsoft.playwright.*; -... +// ... // In the test Page page = browser.newPage(); diff --git a/docs/src/release-notes-java.md b/docs/src/release-notes-java.md index ae9735861129c..908a7683574c5 100644 --- a/docs/src/release-notes-java.md +++ b/docs/src/release-notes-java.md @@ -378,7 +378,7 @@ New method [`method: Page.addLocatorHandler`] registers a callback that will be // Setup the handler. page.addLocatorHandler( page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Hej! You are in control of your cookies.")), - () - > { + () -> { page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all")).click(); }); // Write the test as usual. @@ -1187,14 +1187,12 @@ Playwright for Java 1.18 introduces [Web-First Assertions](./test-assertions). Consider the following example: ```java -... import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; public class TestExample { - ... @Test void statusBecomesSubmitted() { - ... + // ... page.locator("#submit-button").click(); assertThat(page.locator(".status")).hasText("Submitted"); } @@ -1471,19 +1469,19 @@ button.click("button >> visible=true"); Traces are recorded using the new [`property: BrowserContext.tracing`] API: ```java -Browser browser = chromium.launch(); +Browser browser = playwright.chromium().launch(); BrowserContext context = browser.newContext(); // Start tracing before creating / navigating a page. -context.tracing.start(new Tracing.StartOptions() +context.tracing().start(new Tracing.StartOptions() .setScreenshots(true) - .setSnapshots(true); + .setSnapshots(true)); Page page = context.newPage(); -page.goto("https://playwright.dev"); +page.navigate("https://playwright.dev"); // Stop tracing and export it into a zip archive. -context.tracing.stop(new Tracing.StopOptions() +context.tracing().stop(new Tracing.StopOptions() .setPath(Paths.get("trace.zip"))); ``` diff --git a/docs/src/screenshots.md b/docs/src/screenshots.md index d1c7e0e04f8e6..ba50a852a45a0 100644 --- a/docs/src/screenshots.md +++ b/docs/src/screenshots.md @@ -21,7 +21,7 @@ page.screenshot(path="screenshot.png") ```java page.screenshot(new Page.ScreenshotOptions() - .setPath(Paths.get("screenshot.png"))) + .setPath(Paths.get("screenshot.png"))); ``` ```csharp diff --git a/docs/src/test-runners-java.md b/docs/src/test-runners-java.md index 1aaa7135c4f44..ed1d3d81e5e18 100644 --- a/docs/src/test-runners-java.md +++ b/docs/src/test-runners-java.md @@ -202,7 +202,7 @@ You can use a Gradle build configuration script, written in Groovy or Kotlin. }> <TabItem value="gradle"> -```java +```groovy plugins { application id 'java' @@ -234,7 +234,7 @@ test { </TabItem> <TabItem value="gradle-kotlin"> -```java +```groovy plugins { application id("java") diff --git a/utils/doclint/linting-code-snippets/cli.js b/utils/doclint/linting-code-snippets/cli.js index da193e5876795..146646a1f0312 100644 --- a/utils/doclint/linting-code-snippets/cli.js +++ b/utils/doclint/linting-code-snippets/cli.js @@ -209,6 +209,16 @@ class CSharpLintingService extends LintingService { } } +class JavaLintingService extends LintingService { + supports(codeLang) { + return codeLang === 'java'; + } + + async lint(snippets) { + return await this.spawnAsync('java', ['-jar', path.join(__dirname, 'java', 'target', 'java-syntax-checker-1.0-SNAPSHOT.jar')], snippets, path.join(__dirname, 'java')) + } +} + class LintingServiceFactory { constructor() { /** @type {LintingService[]} */ @@ -219,6 +229,7 @@ class LintingServiceFactory { this.services.push( new PythonLintingService(), new CSharpLintingService(), + new JavaLintingService(), ); } this._metrics = {}; diff --git a/utils/doclint/linting-code-snippets/java/.gitignore b/utils/doclint/linting-code-snippets/java/.gitignore new file mode 100644 index 0000000000000..8b8c81d7318df --- /dev/null +++ b/utils/doclint/linting-code-snippets/java/.gitignore @@ -0,0 +1,2 @@ +target/ +dependency-reduced-pom.xml diff --git a/utils/doclint/linting-code-snippets/java/pom.xml b/utils/doclint/linting-code-snippets/java/pom.xml new file mode 100644 index 0000000000000..ec040ad677da8 --- /dev/null +++ b/utils/doclint/linting-code-snippets/java/pom.xml @@ -0,0 +1,53 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.example</groupId> + <artifactId>java-syntax-checker</artifactId> + <version>1.0-SNAPSHOT</version> + + <properties> + <maven.compiler.source>15</maven.compiler.source> + <maven.compiler.target>15</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencies> + <dependency> + <groupId>com.github.javaparser</groupId> + <artifactId>javaparser-core</artifactId> + <version>3.26.2</version> + </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.11.0</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>3.5.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>JavaSyntaxChecker</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file diff --git a/utils/doclint/linting-code-snippets/java/src/main/java/JavaSyntaxChecker.java b/utils/doclint/linting-code-snippets/java/src/main/java/JavaSyntaxChecker.java new file mode 100644 index 0000000000000..be2a5bbe35765 --- /dev/null +++ b/utils/doclint/linting-code-snippets/java/src/main/java/JavaSyntaxChecker.java @@ -0,0 +1,110 @@ +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.github.javaparser.JavaParser; +import com.github.javaparser.Problem; +import com.github.javaparser.ParseResult; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.StaticJavaParser; + +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class JavaSyntaxChecker { + public static void main(String[] args) { + if (args.length == 0) { + System.out.println("Error: Please provide the path to the JSON file"); + return; + } + + String codeSnippetsPath = args[args.length - 1]; + List<CodeSnippet> codeSnippets = readCodeSnippets(codeSnippetsPath); + if (codeSnippets == null) { + System.out.println("Error: codeSnippets is null"); + return; + } + + List<Map<String, Object>> output = new ArrayList<>(); + + ParserConfiguration config = new ParserConfiguration(); + config.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17); + + for (CodeSnippet codeSnippet : codeSnippets) { + String cleanedCode = cleanSnippet(codeSnippet.code); + ParseResult<CompilationUnit> parseResult = new JavaParser(config).parse(cleanedCode); + List<Problem> syntaxErrors = parseResult.getProblems(); + + if (!syntaxErrors.isEmpty()) { + output.add(Map.of( + "status", "error", + "error", String.join("\n", syntaxErrors.stream() + .map(Problem::getMessage) + .collect(Collectors.toList())) + )); + } else { + output.add(Map.of("status", "ok")); + } + } + + System.out.println(new Gson().toJson(output)); + } + + private static String removeImports(String code) { + // Remove import statements + return Pattern.compile("^import.*;$", Pattern.MULTILINE) + .matcher(code) + .replaceAll(""); + } + + private static String cleanSnippet(String code) { + // if it contains "public class" then it's a full class, return immediately + if (code.contains("public class")) { + return code; + } + code = removeImports(code); + String wrappedCode = """ + import com.microsoft.playwright.*; + import static com.microsoft.playwright.assertions.PlaywrightAssertions.*; + + public class Example { + public static void main(String[] args) { + try (Playwright playwright = Playwright.create()) { + Browser browser = playwright.chromium().launch(); + BrowserContext context = browser.newContext(); + Page page = context.newPage(); + %s + } + } + } + """.formatted(code); + return wrappedCode; + } + + private static List<CodeSnippet> readCodeSnippets(String filePath) { + try (FileReader reader = new FileReader(filePath)) { + Type listType = new TypeToken<ArrayList<CodeSnippet>>(){}.getType(); + return new Gson().fromJson(reader, listType); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} + +class CodeSnippet { + String filePath; + String codeLang; + String code; + + public CodeSnippet(String filePath, String codeLang, String code) { + this.filePath = filePath; + this.codeLang = codeLang; + this.code = code; + } +} \ No newline at end of file