Automation Scenarios & Debugging

Selected sample questions and full answers from this section. Sl. No starts from 1 for this page.

These are free sample questions. The complete ebook contains the full structured coverage across 1876 questions.

Buy Full Ebook

Question List

  1. How do you validate toast notifications in Playwright?
  2. How do you wait for an element to disappear after submitting a form?
  3. How do you test progress indicators during file upload?
  4. How do you test drag-and-drop file upload areas?
  5. How would you test a form that saves data asynchronously?
  6. How do you test whether a modal traps keyboard focus correctly?
  7. How would you validate that a modal closes after saving?
  8. How do you switch between multiple pages in Playwright Java?
  9. How would you test role-based access using Playwright Java?
  10. How would you test access to a protected page without login?
  11. How do you test forbidden access scenarios for role-based users?
  12. How do you test UI updates that happen without full page reload?
  13. How would you test frontend behavior when an API returns an empty response?
  14. A critical end-to-end test is slow because it depends on real backend APIs. Would you mock the APIs?
  15. How would you test an empty-state UI using route mocking?
  16. How do you test responsive UI behavior across desktop, tablet, and mobile profiles in Playwright Java?
  17. How would you test a tooltip using Playwright Java?
  18. How do you validate that duplicate API calls are not triggered by a single UI action?
  19. How does `page.pause()` help during local Playwright Java debugging?
  20. An element is visible on screen, but `getByRole()` cannot find it. How would you debug this?
  21. How would you debug locator failures inside web components or Shadow DOM?
  22. A test fails because the DOM element is detached during interaction. How would you solve it?
  23. A mocked response from one test affects another test. What is the likely design issue?

Full answers from the sample content

Read the selected questions and answers below.

1. Automation scenarios

Part I - Core Questions

Question 1.35

How do you validate toast notifications in Playwright?

Interview-Style Answer

I would validate toast notifications by locating the toast message using a stable role, text content, or test ID, then asserting the exact notification text and visible state. If the toast is expected to auto-close, I would also assert that it disappears after the expected duration.

In Playwright Java, this ensures the test verifies both the correct user-visible feedback and its lifecycle. This approach avoids flakiness when multiple toasts may appear or overlap.

Detailed Explanation

Toast notifications are transient and may appear dynamically after user actions. Proper validation should ensure that the correct toast is matched and no unrelated notification is accidentally captured.

Key checks:

1. Correct toast text appears for the action.
2. Correct toast type or status (success, error, warning) if user-visible.
3. Toast is triggered by the intended user action.
4. Toast disappears automatically if expected.
5. No old or unrelated toast is matched.
6. Multiple simultaneous toasts are handled correctly.

Example:

// Trigger the action that shows the toast
page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Submit")
).click();

// Locate the toast notification
Locator toast = page.getByRole(AriaRole.STATUS)
    .filter(new Locator.FilterOptions().setHasText("Submitted successfully"));

// Assert the toast is visible
PlaywrightAssertions.assertThat(toast).isVisible();

// Optional: Assert toast disappears if auto-close is expected
PlaywrightAssertions.assertThat(toast).isHidden();

For multiple toasts:

List<Locator> toasts = page.locator(".toast").all();
for (Locator t : toasts) {
    System.out.println(t.innerText());
}

Common mistake: Selecting the first toast on the page without scoping to the current user action, which can cause validation of an old or unrelated notification and make the test flaky.


Question 1.46

How do you wait for an element to disappear after submitting a form?

Interview-Style Answer

I would use assertThat(locator).isHidden() if the element should remain in the DOM but become invisible, or assertThat(locator).isDetached() if the element should be removed from the DOM entirely. After the element disappears, I would also validate the final expected outcome, such as a success message, updated record, or refreshed table.

This ensures the test proves both that the transient element is gone and that the user-visible result is correct.

Detailed Explanation

UI elements can disappear in two ways:

  • Hidden: The element stays in the DOM but is visually hidden (e.g., a loading spinner fading out).
  • Detached: The element is removed from the DOM (e.g., a modal or temporary notification).

The assertion you choose depends on the application’s behavior.

Example for a hidden spinner:

Locator spinner = page.getByTestId("saving-spinner");

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Submit")
).click();

// Wait for the spinner to become hidden
PlaywrightAssertions.assertThat(spinner).isHidden();

// Validate final result
PlaywrightAssertions.assertThat(
    page.getByText("Saved successfully")
).isVisible();

Example for a detached modal:

Locator modal = page.getByRole(
    AriaRole.DIALOG,
    new Page.GetByRoleOptions().setName("Edit Customer")
);

modal.getByRole(AriaRole.BUTTON, new Locator.GetByRoleOptions().setName("Save")).click();

// Wait for the modal to be removed from the DOM
PlaywrightAssertions.assertThat(modal).isDetached();

// Validate saved changes
PlaywrightAssertions.assertThat(page.getByText("Customer updated successfully")).isVisible();

Key points:

- Choose `isHidden()` vs `isDetached()` based on how the element disappears.
- Always validate the final visible outcome, not only the transient element.
- Use web-first assertions instead of fixed sleeps for stability.

Common mistake: Waiting only for the spinner or modal to disappear without checking that the actual operation completed successfully, which can result in false-positive tests.


Question 1.53

How do you test progress indicators during file upload?

Interview-Style Answer

I would test upload progress by making the upload take long enough to observe the intermediate state, then assert the full state transition: progress indicator appears after upload starts, remains visible while upload is in progress, disappears after completion, and the final success state is shown.

I would not validate exact timing or exact percentage unless the product specifically requires it. The reliable validation is that the user receives correct upload feedback and the UI does not mark the upload as complete too early.

Detailed Explanation

Progress indicators during file upload are important when users need feedback for large files, slow networks, or document-heavy workflows such as invoice upload, resume upload, profile photo upload, or report attachment upload.

A good test should not check only the final success message. It should validate the upload lifecycle:

1. File upload starts.
2. Progress indicator appears.
3. Upload is not shown as complete too early.
4. Progress indicator disappears after completion.
5. Uploaded file name, success message, or attachment row appears.
6. Progress indicator does not remain stuck.

To make the progress state testable, the upload request can be intercepted and delayed in a controlled way:

page.route("**/api/upload", route -> {
    try {
        Thread.sleep(1000);

        route.fulfill(new Route.FulfillOptions()
            .setStatus(200)
            .setContentType("application/json")
            .setBody("{\"status\":\"uploaded\",\"fileName\":\"invoice.pdf\"}"));
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        route.abort();
    }
});

Then trigger the file upload:

page.locator("input[type='file']")
    .setInputFiles(Paths.get("src/test/resources/invoice.pdf"));

Now assert the progress indicator and final result:

Locator progress = page.locator("[role='progressbar']");

PlaywrightAssertions.assertThat(progress).isVisible();

PlaywrightAssertions.assertThat(
    page.getByText("Upload complete")
).isVisible();

PlaywrightAssertions.assertThat(progress).isHidden();

PlaywrightAssertions.assertThat(
    page.getByText("invoice.pdf")
).isVisible();

If the product shows percentage progress, the test can assert broad meaningful behavior such as progress appears or reaches completion, but it should avoid brittle millisecond-based checks. Network speed, browser behavior, CI load, and file size can affect exact timing.

Failure behavior should be tested separately by returning an error response and asserting a visible error message, retry option, or failed upload state. The success test should prove the normal transition; the failure test should prove the error path.

Common mistake: Validating only the final “Upload complete” message while missing broken progress behavior, such as the progress bar never appearing, disappearing too early, staying stuck after completion, or showing success before the upload response is actually handled.


Question 1.56

How do you test drag-and-drop file upload areas?

Interview-Style Answer

If the drag-and-drop upload area is backed by a real file input, I would prefer setInputFiles() because it is stable, avoids native OS dialogs, and validates the actual file-upload path used by the application.

If the product specifically requires drag-and-drop behavior, I would separately validate the drop-zone UI behavior, such as drag-over styling, accepted file feedback, rejected file messages, and whether the dropped file appears in the upload list. The main workflow should still assert the user-visible upload result, not only that a file was attached.

Detailed Explanation

Many drag-and-drop upload components are visually custom, but internally they still use a hidden <input type="file">. In that case, the most reliable Playwright Java approach is to set the file directly on the input.

page.locator("input[type='file']")
    .setInputFiles(Paths.get("src/test/resources/profile.png"));

PlaywrightAssertions.assertThat(
    page.getByText("profile.png")
).isVisible();

PlaywrightAssertions.assertThat(
    page.getByText("Upload successful")
).isVisible();

This avoids OS-level file picker or physical drag-and-drop automation, which is not reliable in browser automation. The test should then validate the business behavior: the file name appears, upload starts, validation passes, and the final success state is shown.

A stronger drag-and-drop upload test should cover:

- Valid file upload
- Uploaded filename displayed
- File type validation
- File size validation
- Remove-file option
- Upload progress or processing state if applicable
- Upload success or error message

For example, to validate file-type rejection:

page.locator("input[type='file']")
    .setInputFiles(Paths.get("src/test/resources/invalid.exe"));

PlaywrightAssertions.assertThat(
    page.getByText("Only PNG, JPG, and PDF files are allowed")
).isVisible();

If the requirement is specifically to test the drop-zone behavior, then the test may need to simulate lower-level drag-and-drop events or use a helper that creates a DataTransfer object in the browser context. That should be used only when the visual drop behavior itself is the product requirement, because it is more complex and less readable than setInputFiles().

The final assertion should always prove the user outcome: file accepted, file rejected, upload completed, upload failed, or file removed. Testing only the drag gesture is not enough.

Common mistake: Trying to automate the operating-system drag-and-drop action or file chooser when the upload area already has an underlying file input that can be tested more reliably with setInputFiles().


Question 1.68

How would you test a form that saves data asynchronously?

Interview-Style Answer

I would test an asynchronously saved form by submitting the form and waiting for the actual user-visible save result instead of using Thread.sleep().

If the save depends on an API call, I may use waitForResponse() to confirm the relevant request completed successfully. But the final validation should still be based on the UI result, such as a success toast, updated record, saved status, enabled button, or refreshed data shown to the user.

Detailed Explanation

Asynchronous saving is common in modern web applications. After clicking Save, the application may show a spinner, disable the Save button, send an API request, update the UI after the response, and then show a success message.

A reliable test should validate this full save behavior:

Response response = page.waitForResponse(
    res -> res.url().contains("/api/customers")
        && res.status() == 200,
    () -> {
        page.getByRole(
            AriaRole.BUTTON,
            new Page.GetByRoleOptions().setName("Save")
        ).click();
    }
);

After the response, assert the visible result:

PlaywrightAssertions.assertThat(
    page.getByText("Saved successfully")
).isVisible();

PlaywrightAssertions.assertThat(
    page.getByText("Ramesh Kumar")
).isVisible();

If the application shows a saving state, the test can validate that the user gets proper feedback during the async operation:

Locator saveButton = page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Save")
);

saveButton.click();

PlaywrightAssertions.assertThat(
    page.getByText("Saving...")
).isVisible();

PlaywrightAssertions.assertThat(
    page.getByText("Saved successfully")
).isVisible();

PlaywrightAssertions.assertThat(saveButton).isEnabled();

The test should also confirm that no error message remains after a successful save:

PlaywrightAssertions.assertThat(
    page.getByText("Unable to save")
).not().isVisible();

Good async save validation includes:

1. Save action is triggered.
2. Saving or loading state appears if required.
3. Relevant API response succeeds if the test is checking the network flow.
4. Success message or saved status appears.
5. Updated data is visible.
6. Save button returns to the expected state.
7. No stale error message remains.

For failure scenarios, I would test separately by mocking or controlling the failed response and asserting the visible error message, retry option, or unsaved state.

Common mistake: Using Thread.sleep() after clicking Save instead of waiting for the real save condition, such as the API response, success toast, updated record, or Save button returning to its normal state.


Question 1.83

How do you test whether a modal traps keyboard focus correctly?

Interview-Style Answer

To test whether a modal traps keyboard focus correctly, I would open the modal using a normal user action, navigate with the keyboard, and assert that focus stays inside the modal until it is closed. This validates that keyboard users cannot tab into background page content while the modal is active.

In Playwright Java, I would use role-based locators to open the dialog, assert that the dialog is visible, press Tab or Shift+Tab, and verify that document.activeElement remains inside the element with role="dialog". I would also verify that closing the modal using Escape or the close button returns focus to the original trigger when that is the expected behavior.

Detailed Explanation

A modal dialog should trap keyboard focus while it is open. This means that when the user presses Tab, focus should move only between interactive elements inside the modal. It should not move to links, buttons, or form fields behind the modal because that background content is visually unavailable or inactive.

Example:

Locator openDialog = page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Open dialog")
);

openDialog.click();

Locator dialog = page.getByRole(AriaRole.DIALOG);

PlaywrightAssertions.assertThat(dialog).isVisible();

page.keyboard().press("Tab");

Boolean focusInsideDialog = (Boolean) page.evaluate(
    "() => document.activeElement.closest('[role=dialog]') !== null"
);

Assertions.assertTrue(focusInsideDialog);

This verifies that after keyboard navigation, the focused element is still inside the modal.

For stronger validation, the test can press Tab multiple times to ensure focus cycles inside the modal and does not escape to the background page:

for (int i = 0; i < 5; i++) {
    page.keyboard().press("Tab");

    Boolean isFocusInsideDialog = (Boolean) page.evaluate(
        "() => document.activeElement.closest('[role=dialog]') !== null"
    );

    Assertions.assertTrue(isFocusInsideDialog);
}

The test should also validate the closing behavior if the product supports closing with Escape:

page.keyboard().press("Escape");

PlaywrightAssertions.assertThat(dialog).isHidden();

Assertions.assertTrue(openDialog.isFocused());

This confirms that the modal closed and focus returned to the original trigger. If the application intentionally returns focus to another logical element, that behavior should be documented and tested.

This type of test is important because a modal that is visually correct can still be inaccessible if keyboard focus escapes behind it. A user relying on the keyboard or a screen reader may lose context or interact with inactive background controls.

Common mistake: checking only that the modal is visible. A complete modal accessibility test should also verify keyboard focus trapping while the modal is open and correct focus return after the modal is closed.


Question 1.86

How would you validate that a modal closes after saving?

Interview-Style Answer

I would perform the save action inside the modal, then assert that the modal is no longer visible. I would also validate that the underlying page reflects the saved changes, such as updated text, table values, or success messages, ensuring the save action truly completed.

This approach confirms both the modal’s disappearance and the business outcome, making the test meaningful and robust.

Detailed Explanation

Simply checking that the modal is hidden is insufficient because the user-visible result may not have been applied. A complete validation ensures the operation succeeded and the page updated correctly.

Example:

Locator modal = page.getByRole(
    AriaRole.DIALOG,
    new Page.GetByRoleOptions().setName("Edit Customer")
);

// Fill a field
modal.getByLabel("Customer Name").fill("Ramesh Kumar");

// Click Save inside the modal
modal.getByRole(
    AriaRole.BUTTON,
    new Locator.GetByRoleOptions().setName("Save")
).click();

// Assert the modal is closed
PlaywrightAssertions.assertThat(modal).isHidden();

// Assert the success message is visible
PlaywrightAssertions.assertThat(
    page.getByText("Customer updated successfully")
).isVisible();

// Assert the updated value is displayed on the page
PlaywrightAssertions.assertThat(
    page.getByText("Ramesh Kumar")
).isVisible();

Good validations include:

- Modal is no longer visible after save
- Success message or toast appears
- Updated data is reflected in the UI
- Any dependent components refresh correctly
- Focus and accessibility state return appropriately

Common mistake: Asserting only that the modal disappeared without verifying that the save action updated the underlying page or data correctly.


Question 1.110

How do you switch between multiple pages in Playwright Java?

Interview-Style Answer

In Playwright Java, you do not switch pages using Selenium-style window handles. You keep explicit Page references and interact with the correct Page object directly.

When a new tab or popup opens, Playwright returns a new Page object through methods such as page.waitForPopup() or browserContext.waitForPage(). The original page remains available through its existing reference, so the test can move between pages by using the appropriate Page variable.

Detailed Explanation

Playwright uses a page-reference model. Each browser tab or popup is represented as a Page object. Instead of switching by index or window handle, the test stores the returned page reference and performs actions on that object.

Example:

Page originalPage = page;

Page popup = originalPage.waitForPopup(() -> {
    originalPage.getByText("Open Details").click();
});

PlaywrightAssertions.assertThat(
    popup.getByText("Details")
).isVisible();

popup.close();

PlaywrightAssertions.assertThat(
    originalPage.getByText("Dashboard")
).isVisible();

In this example, originalPage represents the starting page, and popup represents the newly opened tab or popup. After validating the popup, the test closes it and continues using the original page reference.

For context-level page creation, browserContext.waitForPage() can also be used:

Page newPage = context.waitForPage(() -> {
    page.getByRole(
        AriaRole.LINK,
        new Page.GetByRoleOptions().setName("Open Report")
    ).click();
});

PlaywrightAssertions.assertThat(
    newPage.getByRole(
        AriaRole.HEADING,
        new Page.GetByRoleOptions().setName("Report")
    )
).isVisible();

This approach is clearer and safer than switching by index because each variable describes the page being controlled. It also avoids accidentally performing actions on the wrong tab.

A good test should validate something meaningful on each page, such as URL, title, heading, or important content:

PlaywrightAssertions.assertThat(newPage).hasURL(Pattern.compile(".*report.*"));

If the popup is no longer needed, close it to keep the test clean and avoid later confusion.

Common mistake: losing the original page reference or continuing actions on the wrong Page object. In Playwright Java, reliable multi-page handling depends on storing clear page references and interacting with the correct page directly.


Question 1.115

How would you test role-based access using Playwright Java?

Interview-Style Answer

I would test role-based access by logging in as each role or loading a role-specific storage-state file, then validating both allowed and restricted behavior.

For each role, I would check visible permissions, hidden or disabled restricted actions, blocked direct URL access, and the expected business outcome. For example, an Admin may see User Management and create users, while a Viewer should not see the Create User button and should be redirected or shown an access-denied message when opening the User Management URL directly.

Detailed Explanation

Role-based access testing should cover both positive and negative permissions. Testing only the Admin role is not enough because most access-control defects appear in restricted roles.

A practical setup uses separate storage-state files or login flows for each role:

BrowserContext adminContext = browser.newContext(
    new Browser.NewContextOptions()
        .setStorageStatePath(Paths.get("auth/admin.json"))
);

BrowserContext viewerContext = browser.newContext(
    new Browser.NewContextOptions()
        .setStorageStatePath(Paths.get("auth/viewer.json"))
);

Page adminPage = adminContext.newPage();
Page viewerPage = viewerContext.newPage();

For the Admin role, the test should validate allowed UI and business behavior:

adminPage.navigate(baseUrl + "/users");

PlaywrightAssertions.assertThat(
    adminPage.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Create User")
    )
).isVisible();

adminPage.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Create User")
).click();

PlaywrightAssertions.assertThat(
    adminPage.getByRole(
        AriaRole.DIALOG,
        new Page.GetByRoleOptions().setName("Create User")
    )
).isVisible();

For the Viewer role, the same restricted action should be hidden, disabled, or unavailable according to the product rule:

viewerPage.navigate(baseUrl + "/users");

PlaywrightAssertions.assertThat(
    viewerPage.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Create User")
    )
).isHidden();

The test should also validate direct URL access because hiding a menu item does not prove access control. A restricted user should not be able to open protected pages directly:

viewerPage.navigate(baseUrl + "/users/create");

PlaywrightAssertions.assertThat(
    viewerPage.getByText("Access denied")
).isVisible();

For record-level permissions, locators should be scoped to the correct row or card. For example, a Manager may edit assigned records but not other users’ records:

Locator row = viewerPage.getByRole(
    AriaRole.ROW,
    new Page.GetByRoleOptions().setName("Request 1001 Approved")
);

PlaywrightAssertions.assertThat(
    row.getByRole(
        AriaRole.BUTTON,
        new Locator.GetByRoleOptions().setName("Edit")
    )
).isHidden();

Role-based UI testing should not be treated as complete security testing. It validates what the user sees and can do through the UI, but backend authorization should also be tested separately to ensure restricted users cannot perform protected actions by calling APIs directly.

Common mistake: Testing only the Admin happy path and assuming role-based access works, while missing restricted-role checks for hidden actions, disabled controls, direct URL access, and record-level permissions.


Question 1.116

How would you test access to a protected page without login?

Interview-Style Answer

I would test protected-page access by opening the protected URL in a fresh unauthenticated BrowserContext and asserting that the user is redirected to the login page or shown an access-denied message.

The test should also verify that protected content is not visible, no sensitive data is exposed, and the original requested URL is preserved if the product supports post-login redirect. Using a fresh context is important because it avoids accidentally reusing cookies, tokens, local storage, or storage state from a previously logged-in test.

Detailed Explanation

Protected-page testing validates that unauthenticated users cannot access restricted application areas by directly typing the URL. This is different from normal logged-in navigation because it checks the security boundary before authentication.

A reliable test should start with a fresh browser context:

BrowserContext context = browser.newContext();
Page page = context.newPage();

page.navigate(baseUrl + "/admin/users");

PlaywrightAssertions.assertThat(page)
    .hasURL(Pattern.compile(".*/login"));

PlaywrightAssertions.assertThat(
    page.getByText("Please sign in")
).isVisible();

The test should also assert that protected content is not visible:

PlaywrightAssertions.assertThat(
    page.getByRole(
        AriaRole.HEADING,
        new Page.GetByRoleOptions().setName("User Management")
    )
).not().isVisible();

If the application preserves the originally requested URL for post-login redirect, validate that behavior too. For example, the login page may contain a redirect query parameter:

PlaywrightAssertions.assertThat(page)
    .hasURL(Pattern.compile(".*/login.*redirect=.*admin/users.*"));

For applications that show an access-denied page instead of redirecting to login, the assertion should match the product rule:

PlaywrightAssertions.assertThat(
    page.getByText("Access denied")
).isVisible();

A strong protected-route test should be isolated and parallel-safe. It should not reuse an authenticated BrowserContext, role-specific storage state, or a page from a previous test. Otherwise, the test may pass or fail for the wrong reason because old authentication data is still present.

Good checks include:

1. Protected content is not visible.
2. User is redirected to login or shown access denied.
3. Original requested URL is preserved if expected.
4. No sensitive data is shown before redirect.
5. The test uses a fresh unauthenticated BrowserContext.

Common mistake: Testing protected pages only after login and missing direct unauthenticated URL access, or accidentally reusing an authenticated context so the test does not truly verify access without login.


Question 1.119

How do you test forbidden access scenarios for role-based users?

Interview-Style Answer

I would test forbidden access by logging in as a valid but lower-privileged user, then trying to access a restricted route or action.

Forbidden access is different from unauthenticated access. The user is already authenticated, but does not have the required permission. So the test should validate that the user stays logged in, restricted menus or actions are hidden or disabled as expected, direct URL access is blocked, and the application shows an access-denied or forbidden message without rendering restricted content.

Detailed Explanation

Forbidden access scenarios are common in role-based applications. For example, a Viewer may be allowed to view reports but not manage users. A normal user may access the dashboard but not admin settings. A Manager may edit assigned records but not records owned by another team.

A good test should use a role-specific storage state or login flow for the lower-privileged user:

BrowserContext viewerContext = browser.newContext(
    new Browser.NewContextOptions()
        .setStorageStatePath(Paths.get("auth/viewer.json"))
);

Page viewerPage = viewerContext.newPage();

First, the test can confirm that the user is authenticated:

viewerPage.navigate("https://example.com/dashboard");

PlaywrightAssertions.assertThat(
    viewerPage.getByText("Dashboard")
).isVisible();

Then try to open the restricted route directly:

viewerPage.navigate("https://example.com/admin/users");

PlaywrightAssertions.assertThat(viewerPage)
    .hasURL(Pattern.compile(".*/forbidden|.*/access-denied"));

PlaywrightAssertions.assertThat(
    viewerPage.getByText("Access denied")
).isVisible();

The test should also confirm that restricted content is not rendered:

PlaywrightAssertions.assertThat(
    viewerPage.getByRole(
        AriaRole.HEADING,
        new Page.GetByRoleOptions().setName("User Management")
    )
).not().isVisible();

For menu-based restrictions, the test should verify that restricted actions are hidden or disabled according to the product rule:

viewerPage.navigate("https://example.com/dashboard");

PlaywrightAssertions.assertThat(
    viewerPage.getByRole(
        AriaRole.LINK,
        new Page.GetByRoleOptions().setName("Admin Console")
    )
).isHidden();

For critical permissions, UI validation alone is not enough. Hiding a button or menu item does not prove security. Backend authorization should also reject restricted actions if the user bypasses the UI and calls the endpoint directly.

Each role should use an isolated BrowserContext so cookies, tokens, permissions, and storage state do not leak between admin, manager, viewer, or normal-user tests.

Common mistake: Testing only whether the admin menu is hidden, without checking direct restricted URL access, forbidden-page behavior, absence of protected content, and backend rejection of unauthorized actions.


Question 1.124

How do you test UI updates that happen without full page reload?

Interview-Style Answer

I would trigger the UI action and then assert the specific updated state, such as changed text, added row, updated count, visible toast, changed button state, or refreshed component data.

Modern applications often update the UI through client-side state changes or API responses without full page reload. So I would not wait for navigation unless the product actually changes route.

If the update depends on an API call, I may wait for the relevant response, but I would still validate the final rendered UI result because the user only cares whether the updated state is visible and correct.

Detailed Explanation

Many React, Angular, and Vue applications update parts of the page without reloading the browser document. The URL may remain the same, but the UI changes after a state update, API response, WebSocket message, or background refresh.

Examples include:

1. Table refresh
2. Status update
3. Inline edit
4. Search filter
5. Notification update
6. Cart count update
7. Dashboard widget refresh
8. Save operation showing updated field value

A good test waits for the exact updated UI state:

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Refresh Orders")
).click();

PlaywrightAssertions.assertThat(
    page.getByText("Updated just now")
).isVisible();

Locator row = page.getByRole(AriaRole.ROW)
    .filter(new Locator.FilterOptions().setHasText("ORD-1001"));

PlaywrightAssertions.assertThat(row).isVisible();

For an inline edit, validate the value after save:

Locator row = page.getByRole(AriaRole.ROW)
    .filter(new Locator.FilterOptions().setHasText("ORD-1001"));

row.getByRole(
    AriaRole.BUTTON,
    new Locator.GetByRoleOptions().setName("Edit")
).click();

page.getByLabel("Status").selectOption("Approved");

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Save")
).click();

PlaywrightAssertions.assertThat(row).containsText("Approved");

If the update is API-driven, combine network wait with UI validation:

Response response = page.waitForResponse(
    res -> res.url().contains("/api/orders")
        && res.status() == 200,
    () -> page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Refresh Orders")
    ).click()
);

PlaywrightAssertions.assertThat(
    page.getByText("Updated just now")
).isVisible();

The response confirms that the backend returned, but the locator assertion confirms that the browser rendered the updated state.

Common mistake: Waiting for navigation or page reload when the application updates content in place, instead of asserting the exact changed UI state such as updated text, row, count, status, toast, or refreshed component data.


Question 1.132

How would you test frontend behavior when an API returns an empty response?

Interview-Style Answer

To test frontend behavior when an API returns an empty response, I would mock the API with a valid empty response and verify that the UI shows the correct empty-state behavior. The page should not crash, show stale data, display misleading content, or leave the user with a blank unexplained area.

In Playwright Java, this is useful because it separates frontend empty-state handling from backend data availability. The test controls the API response and then validates the browser-visible result, such as “No orders found,” “No notifications,” “No search results,” or a disabled action button.

Detailed Explanation

Empty responses are common in real applications. A new user may have no orders, a search may return no results, a notification list may be empty, or a customer may have no saved addresses. The frontend should handle these cases safely and clearly.

Example:

page.route("**/api/orders", route -> {
    route.fulfill(new Route.FulfillOptions()
        .setStatus(200)
        .setContentType("application/json")
        .setBody("{\"orders\":[]}"));
});

page.navigate("https://example.com/orders");

PlaywrightAssertions.assertThat(
    page.getByText("No orders found")
).isVisible();

This test validates that the backend returned a legitimate empty result and that the frontend displayed the correct empty-state message.

A stronger empty-state test may also verify that old or misleading data is not shown:

PlaywrightAssertions.assertThat(
    page.getByTestId("orders-table")
).isHidden();

PlaywrightAssertions.assertThat(
    page.getByText("No orders found")
).isVisible();

For search results:

page.route("**/api/search?query=unknown*", route -> {
    route.fulfill(new Route.FulfillOptions()
        .setStatus(200)
        .setContentType("application/json")
        .setBody("{\"results\":[]}"));
});

page.getByLabel("Search").fill("unknown item");

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Search")
).click();

PlaywrightAssertions.assertThat(
    page.getByText("No results found")
).isVisible();

The important point is to connect the mocked API response to a user-visible outcome. The test should prove that the frontend handles the empty response gracefully, with a clear message, correct layout, and no stale records.

Common mistake: testing only data-present scenarios. A reliable frontend test suite should also validate empty API responses so users get meaningful empty-state behavior instead of broken layouts, stale data, or confusing blank screens.


Question 1.138

A critical end-to-end test is slow because it depends on real backend APIs. Would you mock the APIs?

Interview-Style Answer

I would not automatically mock the APIs. If the purpose of the test is release-critical end-to-end confidence, I would keep the real backend path because the integration itself is part of what the test is validating.

Mocks are useful for speed, control, and edge cases, but they reduce integration confidence. For a critical flow such as payment, order creation, approval, authentication, or checkout, mocking the backend may hide the exact risk the end-to-end test is supposed to catch. I may create additional mocked tests for rare errors, empty states, unauthorized responses, or slow third-party behavior, but I would preserve at least one real-backend E2E path for release validation.

Detailed Explanation

A slow end-to-end test should be reviewed carefully before deciding to mock APIs. The right decision depends on the purpose of the test. If the test is meant to validate frontend behavior only, mocks may be appropriate. But if the test is meant to certify that the frontend, backend, authentication, database, business rules, and integrations work together, mocking the backend removes important coverage.

Keep the real backend for flows such as:

- Payment
- Order creation
- Approval workflow
- Authentication
- Checkout
- Critical business integration
- Release smoke validation

Use mocks for scenarios such as:

- Rare server errors
- Empty responses
- Unauthorized or forbidden responses
- Partial or malformed API data
- Slow third-party service behavior
- UI fallback and retry behavior

For example, if the release-critical test validates order creation, the real backend should usually remain part of that test:

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Submit Order")
).click();

PlaywrightAssertions.assertThat(
    page.getByText("Order created successfully")
).isVisible();

Locator orderRow = page.getByTestId("orders-table")
    .getByRole(AriaRole.ROW)
    .filter(new Locator.FilterOptions().setHasText("ORD-1001"));

PlaywrightAssertions.assertThat(orderRow).isVisible();

If the same flow is slow, I would first optimize the test without removing critical integration coverage. For example:

1. Use API setup for preconditions.
2. Remove unnecessary UI steps.
3. Use stable and unique test data.
4. Avoid repeated login by using storage state.
5. Run the critical E2E test only in the release suite.
6. Improve backend test environment reliability.
7. Split edge-case coverage into faster mocked tests.

A balanced framework can have both real-backend and mocked coverage. The real-backend test proves integration confidence. Mocked tests prove frontend behavior for controlled edge cases. This keeps the suite both trustworthy and efficient.

Common mistake: mocking the exact backend behavior that the critical end-to-end test is supposed to validate. Mocks are useful, but they should not remove release-critical integration confidence.


Question 1.148

How would you test an empty-state UI using route mocking?

Interview-Style Answer

I would test an empty-state UI by mocking the relevant API response to return an empty array or empty result set, then asserting that the correct empty-state message appears.

I would also verify that stale data is not shown, table rows or cards are absent, pagination is hidden or disabled if expected, and any allowed recovery action such as Create New or Add Order is visible. This makes the empty-state test deterministic instead of depending on the real backend having no data.

Detailed Explanation

Empty-state UI should be tested with controlled data because relying on the backend to be empty by chance can make the test flaky. In Playwright Java, page.route() can be used to mock the API response before navigating to the page.

Example:

page.route("**/api/orders", route -> {
    route.fulfill(new Route.FulfillOptions()
        .setStatus(200)
        .setContentType("application/json")
        .setBody("[]"));
});

page.navigate(baseUrl + "/orders");

PlaywrightAssertions.assertThat(
    page.getByText("No orders found")
).isVisible();

PlaywrightAssertions.assertThat(
    page.getByTestId("orders-table").locator("tbody tr")
).hasCount(0);

If the real API contract wraps data inside an object, the mock should follow the same contract:

page.route("**/api/orders", route -> {
    route.fulfill(new Route.FulfillOptions()
        .setStatus(200)
        .setContentType("application/json")
        .setBody("""
            {
              "orders": [],
              "total": 0
            }
            """));
});

The test should then validate the complete empty-state behavior:

PlaywrightAssertions.assertThat(
    page.getByText("No orders found")
).isVisible();

PlaywrightAssertions.assertThat(
    page.getByTestId("orders-table-row")
).hasCount(0);

PlaywrightAssertions.assertThat(
    page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Create Order")
    )
).isVisible();

Good empty-state checks include:

1. Empty-state message appears.
2. Table rows, cards, or list items are absent.
3. Pagination is hidden or disabled.
4. Create/new action appears if expected.
5. No stale or cached data remains visible.
6. The mocked response matches the real API contract.

If the empty state appears only after filtering, the test should mock or trigger the filtered API response and assert that the empty message is related to the filter result, not a generic page failure.

Common mistake: Depending on real backend data to be empty by chance, or mocking an unrealistic response shape that does not match the real API contract, causing the test to pass without proving the actual empty-state behavior.


Part II - Additional Questions

Question 1.200

How do you test responsive UI behavior across desktop, tablet, and mobile profiles in Playwright Java?

Interview-Style Answer

I would test responsive UI behavior by defining a small, risk-based set of browser context profiles and running the same critical workflows across those profiles.

In Playwright Java, teams usually implement this through JUnit parameterized tests, TestNG data providers, Maven profiles, Gradle properties, or CI/CD matrix jobs. The test logic should remain reusable, while the BrowserContext configuration changes for desktop, tablet, and mobile-like profiles.

This helps validate whether important user journeys work correctly across screen sizes without creating duplicate test classes for each device type.

Detailed Explanation

Responsive testing should focus on business-critical workflows and layout-sensitive areas, not every test in the suite. For example, login, checkout, search, navigation menus, forms, dashboards, and payment flows may need responsive coverage, while backend-heavy admin flows may not need to run on every profile.

In Playwright Java, the equivalent of device/profile-based execution is usually handled through framework configuration. The test receives a profile name, creates the matching BrowserContext, and then runs the same workflow.

Example:

static Browser.NewContextOptions profile(String name) {
    return switch (name.toLowerCase()) {
        case "mobile" -> new Browser.NewContextOptions()
            .setViewportSize(390, 844)
            .setIsMobile(true)
            .setHasTouch(true)
            .setDeviceScaleFactor(3);

        case "tablet" -> new Browser.NewContextOptions()
            .setViewportSize(768, 1024)
            .setIsMobile(true)
            .setHasTouch(true)
            .setDeviceScaleFactor(2);

        default -> new Browser.NewContextOptions()
            .setViewportSize(1366, 768);
    };
}

The test can then create a context based on the selected profile:

BrowserContext context = browser.newContext(profile("mobile"));
Page page = context.newPage();

page.navigate(baseUrl);

PlaywrightAssertions.assertThat(
    page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Menu")
    )
).isVisible();

A good responsive test should validate both functionality and layout-relevant behavior. For example, on desktop the navigation may be visible as a full menu, while on mobile it may be hidden behind a hamburger button.

Useful responsive validations include:

1. Desktop navigation appears correctly.
2. Mobile hamburger menu opens and closes.
3. Tablet layout does not overlap or clip content.
4. Important buttons remain visible and tappable.
5. Forms are usable on smaller screens.
6. Sticky headers, footers, and modals do not block actions.
7. Critical data remains readable without broken layout.

The same workflow should be reused wherever possible. Only the profile setup and profile-specific assertions should change.

Example idea:

void verifySearchWorks(Page page) {
    page.getByRole(
        AriaRole.SEARCHBOX,
        new Page.GetByRoleOptions().setName("Search")
    ).fill("laptop");

    page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Search")
    ).click();

    PlaywrightAssertions.assertThat(
        page.getByText("Search results")
    ).isVisible();
}

Then this reusable workflow can run against desktop, tablet, and mobile contexts.

It is also important to understand the limits of emulation. Playwright can simulate viewport size, touch support, user agent, and device scale factor, but it is not a complete replacement for real-device testing. Real devices may still reveal issues with mobile browser behavior, virtual keyboard, gestures, performance, rendering, and touch interaction.

Common mistake: Creating separate duplicate test classes for desktop, tablet, and mobile. A better approach is to parameterize the browser context profile and reuse the same workflow, adding only profile-specific assertions where the UI behavior genuinely differs.


Question 1.211

How would you test a tooltip using Playwright Java?

Interview-Style Answer

I would trigger the tooltip the same way a user does, usually by hovering over or focusing the target element, then assert that the expected tooltip text becomes visible.

For icon-only tooltip triggers, I would use a stable accessible name or data-testid, and I would verify that the tooltip belongs to the correct field or control. If the tooltip is accessible, I would prefer getByRole(AriaRole.TOOLTIP) for validation.

I would also test that the tooltip disappears when the pointer moves away or focus is removed, if that is the expected behavior.

Detailed Explanation

Tooltips are often used for help text, validation hints, field descriptions, disabled-action explanations, or icon-only controls. A reliable test should not only check that some tooltip appears. It should prove that the correct tooltip appears for the correct trigger.

Example:

page.getByLabel("Info").hover();

PlaywrightAssertions.assertThat(
    page.getByText("Password must contain at least 8 characters")
).isVisible();

For icon-only tooltip triggers, a data-testid or accessible label is usually better than a fragile CSS selector:

page.getByTestId("password-info-icon").hover();

PlaywrightAssertions.assertThat(
    page.getByRole(AriaRole.TOOLTIP)
).containsText("Password must contain at least 8 characters");

Good tooltip checks include:

1. Tooltip appears on hover or focus.
2. Tooltip text is correct.
3. Tooltip is associated with the correct control.
4. Tooltip disappears when hover moves away if expected.
5. Tooltip does not appear for the wrong field or icon.
6. Keyboard/focus behavior works if accessibility is required.

If the tooltip has a small animation delay, avoid Thread.sleep(). Use a Playwright assertion on the tooltip text or role so the test waits until the tooltip is visible.

To validate disappearance:

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Submit")
).hover();

PlaywrightAssertions.assertThat(
    page.getByRole(AriaRole.TOOLTIP)
).isVisible();

page.mouse().move(0, 0);

PlaywrightAssertions.assertThat(
    page.getByRole(AriaRole.TOOLTIP)
).isHidden();

Common mistake: checking only that a tooltip appears somewhere on the page, without verifying the correct tooltip text, correct trigger element, hover/focus behavior, or disappearance rule.


Question 1.232

How do you validate that duplicate API calls are not triggered by a single UI action?

Interview-Style Answer

I validate duplicate API calls by intercepting and counting network requests triggered by a single user action and asserting that only the expected number of calls (usually one) is made.

This ensures the frontend does not accidentally trigger multiple backend operations due to issues like double-clicks, re-renders, or duplicate event bindings.

Detailed Explanation

Duplicate API calls can lead to serious production issues such as duplicate orders, duplicate payments, or inconsistent application state. These often occur due to:

- Double-click or rapid user interactions
- Missing button disable after first click
- React/Vue re-render triggering multiple handlers
- Incorrect retry or debounce logic
- Multiple event listeners attached accidentally

In Playwright Java, we validate this using request interception and counters.

Example: counting API calls

AtomicInteger createCalls = new AtomicInteger();

page.route("**/api/orders", route -> {
    if ("POST".equals(route.request().method())) {
        createCalls.incrementAndGet();
    }

    route.fulfill(new Route.FulfillOptions()
        .setStatus(201)
        .setContentType("application/json")
        .setBody("{\"id\":\"ORD-1001\"}"));
});

Trigger UI action

page.navigate(appUrl + "/orders/new");

page.getByLabel("Order name").fill("No duplicate order");

Locator createButton = page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Create order")
);

// Simulate aggressive user interaction
createButton.dblclick();

Validate single API call

PlaywrightAssertions.assertThat(
    page.getByText("Order created")
).isVisible();

Assertions.assertEquals(1, createCalls.get());

Strong validation strategy (real projects)

- Assert request count (no duplicates)
- Validate backend state (no duplicate records)
- Ensure UI disables button after first click
- Verify idempotency at API level (where applicable)

Key principle

UI action → should map to exactly one business operation unless explicitly designed otherwise.

Common mistake:

Only clicking once and assuming the system is correct, without validating actual network traffic or backend side effects, which allows hidden duplicate API calls to go unnoticed in production.


2. Debugging

Part I - Core Questions

Question 2.4

How does page.pause() help during local Playwright Java debugging?

Interview-Style Answer

page.pause() pauses test execution at a specific point and opens the Playwright Inspector during local debugging. It lets me inspect the current page state, try locators, verify whether the expected elements are present, and continue the test step by step.

It is useful when I need interactive investigation, especially for locator issues, unexpected page states, overlays, dialogs, navigation problems, or actionability failures. However, it should only be used locally and should not be committed into normal test code because it blocks unattended CI/CD execution.

Detailed Explanation

page.pause() is a local debugging tool in Playwright. When execution reaches this line, Playwright stops the test and opens the Inspector. From there, I can look at the browser state, inspect elements, test selectors, and continue execution manually.

Example:

Browser browser = playwright.chromium().launch(
    new BrowserType.LaunchOptions().setHeadless(false)
);

Page page = browser.newPage();
page.navigate("https://example.com/login");

page.pause();

page.getByLabel("Username").fill("admin");
page.getByLabel("Password").fill("secret");
page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Login")
).click();

This is useful when I want to stop just before the failing step and check what the application really looks like. For example, I can verify whether the login page loaded correctly, whether the username field has the expected accessible label, whether a modal is covering the button, or whether the test is on a different page than expected.

It also helps when debugging locators. I can pause the test, inspect the DOM and accessibility information, and confirm whether a locator such as getByRole(), getByLabel(), getByText(), or locator() is matching the intended element.

For example, if this click fails:

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Submit")
).click();

I can insert page.pause() before it and check whether the button is visible, enabled, covered by another element, renamed, inside an iframe, or not yet rendered.

page.pause() is best for interactive local debugging. For CI failures, Playwright traces, screenshots, videos, console logs, and network details are usually better because they can be collected automatically and reviewed after the run.

Common mistake: leaving page.pause() in committed test code. Since it waits for human interaction, it can block Maven, Gradle, JUnit, TestNG, or CI/CD execution and make the pipeline hang.


Question 2.17

An element is visible on screen, but getByRole() cannot find it. How would you debug this?

Interview-Style Answer

I would debug it by checking what Playwright can identify through the accessibility model, not just what is visually visible on the screen. getByRole() depends on the element’s semantic role and accessible name, so I would verify whether the element is really exposed as the expected role, whether its accessible name matches the locator, and whether it is hidden from the accessibility tree.

I would also check whether the element is inside an iframe, duplicated elsewhere on the page, rendered after an API call, or visually styled to look like a button or link without proper HTML semantics. If the role locator fails, the fix may be improving the application’s accessibility or locator scope, not immediately replacing it with XPath.

Detailed Explanation

A visually visible element is not always findable using getByRole(). Role locators work based on accessibility semantics. For example, Playwright can easily find a real <button> with the accessible name Submit, but it may not find a <div> styled like a button unless the application exposes it correctly with a role and accessible name.

Example:

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Submit")
).click();

If this fails even though the Submit button is visible, I would first inspect the element using Playwright Inspector or Trace Viewer and check:

1. Is the element really exposed as a button, link, checkbox, textbox, or heading?
2. Does the accessible name exactly match "Submit"?
3. Is the visible text different from the accessible name?
4. Is the element hidden using aria-hidden, hidden, display:none, or visibility:hidden?
5. Is the element inside an iframe?
6. Are there multiple matching elements causing strict mode issues?
7. Is the element rendered only after an API response or client-side update?
8. Is another overlay or loading layer covering the real interactive element?

A common issue is that the UI looks like a button but is implemented like this:

<div class="primary-button">Submit</div>

This may look clickable to the user, but it does not have proper button semantics. A better implementation would be:

<button>Submit</button>

or, if custom markup is unavoidable:

<div role="button" aria-label="Submit">Submit</div>

If the visible text and accessible name are different, the locator should use the accessible name. For example, an icon button may visually show only a search icon, but the accessible name may be Search products:

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Search products")
).click();

If the element is inside an iframe, page.getByRole() will not find it from the main page. I should first target the iframe with frameLocator(), then locate the element inside the iframe:

FrameLocator frame = page.frameLocator("iframe[title='Payment']");

frame.getByRole(
    AriaRole.BUTTON,
    new Locator.GetByRoleOptions().setName("Pay")
).click();

Here, frameLocator() targets the iframe, and the chained getByRole() locates the actual button inside that iframe.

If multiple elements match the same role and name, Playwright strict mode may fail. In that case, I should scope the locator to the correct section, dialog, form, card, or table row instead of using a broad page-level locator:

Locator loginForm = page.getByRole(
    AriaRole.FORM,
    new Page.GetByRoleOptions().setName("Login")
);

loginForm.getByRole(
    AriaRole.BUTTON,
    new Locator.GetByRoleOptions().setName("Submit")
).click();

If the element appears after an API call, I would use a web-first assertion before interacting:

Locator submitButton = page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Submit")
);

PlaywrightAssertions.assertThat(submitButton).isVisible();
submitButton.click();

If the role locator failure reveals that the application has poor accessibility, I would raise that as a product issue. Role-based locators are valuable because they encourage tests to interact with the application like a real user and make the test more maintainable.

Common mistake: replacing a failed getByRole() locator with a long XPath without checking the actual role, accessible name, iframe context, visibility, and strict mode behavior. A failed role locator often points to an accessibility, scoping, or semantic HTML issue that should be understood first.


Question 2.22

How would you debug locator failures inside web components or Shadow DOM?

Interview-Style Answer

I would first check whether the web component uses open or closed Shadow DOM, because Playwright can work with open Shadow DOM but cannot pierce closed Shadow DOM internals directly.

For debugging, I would prefer user-facing locators such as getByRole(), getByLabel(), getByText(), or getByTestId() where possible. If the component exposes proper roles, labels, and accessible names, the test can validate behavior without depending on fragile internal markup.

Detailed Explanation

Web components can hide their internal DOM structure behind Shadow DOM. If the Shadow DOM is open, Playwright can usually locate elements using normal locators when the elements are accessible through the composed tree. If the Shadow DOM is closed, the internal elements are intentionally hidden from automation and page scripts, so the test should interact through the public behavior of the component instead of trying to access private internals.

A good debugging checklist is:

1. Check whether the component uses open or closed Shadow DOM.
2. Inspect whether the target element has a proper role and accessible name.
3. Try role, label, text, or test-id locators first.
4. Check whether the locator matches zero, one, or multiple elements.
5. Scope the locator to the component or surrounding section when duplicates exist.
6. Avoid long CSS paths into component internals.
7. Validate the visible behavior of the component, not private markup.

Example of a user-facing locator:

page.getByRole(
    AriaRole.BUTTON,
    new Page.GetByRoleOptions().setName("Choose date")
).click();

PlaywrightAssertions.assertThat(
    page.getByText("May 2026")
).isVisible();

This is better than relying on internal implementation details such as:

page.locator("date-picker >>> div.calendar > button.next").click();

A better test focuses on the component’s public behavior. For example, if the date picker opens a calendar and allows selecting a date, the test should assert that the calendar appears, the expected month is shown, and the selected date is reflected in the input or page state.

If the locator fails, I would inspect the component in Playwright Inspector or Trace Viewer and check whether the visible text is actually exposed as an accessible name. If not, the issue may be an accessibility problem in the component, not only a test issue.

For custom controls, adding stable accessible names or test IDs at the component boundary is often better than exposing internal structure. This keeps tests stable even if the component implementation changes.

Common mistake: using long internal CSS paths inside web components. Such locators break easily when the component markup changes; role, label, text, or test-id locators aimed at public component behavior are usually more reliable.


Question 2.30

A test fails because the DOM element is detached during interaction. How would you solve it?

Interview-Style Answer

I would treat this as a re-rendering or stale DOM reference problem. In Playwright Java, the best approach is to use Locator instead of storing an element too early, because a Locator re-resolves the matching element when the action or assertion is performed.

I would also check why the DOM is changing during the interaction, such as React/Angular/Vue re-rendering, grid refresh, filtering, sorting, lazy loading, animation, or auto-refresh. Then I would wait for the UI to reach a stable application state before interacting with the element.

Detailed Explanation

A detached element failure usually means the test found an element, but before Playwright completed the action, that DOM node was removed and replaced by the application. To the user, the button or row may look the same, but internally it is a new DOM element.

This commonly happens in modern applications with:

- React, Angular, or Vue re-rendering
- Virtualized tables or grids
- Sorting or filtering
- Auto-refreshing lists
- Skeleton loaders replaced by real content
- Dropdowns or modals being recreated
- Conditional rendering after API responses
- Animations or layout transitions

The wrong approach is to capture an element-like reference too early and reuse it after the page updates. A safer approach is to keep the interaction locator-based:

Locator row = page.getByTestId("orders-grid")
    .getByRole(AriaRole.ROW)
    .filter(new Locator.FilterOptions().setHasText("ORD-1001"));

PlaywrightAssertions.assertThat(row).isVisible();

row.getByRole(
    AriaRole.BUTTON,
    new Locator.GetByRoleOptions().setName("Approve")
).click();

PlaywrightAssertions.assertThat(
    page.getByTestId("order-status")
).hasText("Approved");

Here, the row and button are located through Locator chains. Playwright resolves them when the assertion or click runs, instead of depending on an old DOM node.

I would also wait for the real readiness condition before clicking:

PlaywrightAssertions.assertThat(
    page.getByTestId("orders-loading")
).not().isVisible();

PlaywrightAssertions.assertThat(row).isVisible();

If the detachment happens because the row refreshes after an API call, I would wait for the relevant response and then assert the final UI state:

Response response = page.waitForResponse(
    r -> r.url().contains("/api/orders")
        && r.status() == 200,
    () -> page.getByRole(
        AriaRole.BUTTON,
        new Page.GetByRoleOptions().setName("Refresh")
    ).click()
);

PlaywrightAssertions.assertThat(row).isVisible();

This fixes the real issue: the test interacts only after the currently rendered UI is ready.

Common mistake: Storing an element reference too early or reusing it after the application re-renders, instead of using Locator chains and waiting for the stable UI state before interaction.


Question 2.45

A mocked response from one test affects another test. What is the likely design issue?

Interview-Style Answer

The likely design issue is route leakage caused by a mock being registered too broadly, globally, or on a shared BrowserContext. Network mocks should normally be scoped to the test that needs them. If one test’s mocked response affects another test, the framework may be reusing browser state, keeping routes active across tests, or using a URL pattern that intercepts unrelated requests.

I would fix this by creating a fresh BrowserContext per test, registering mocks only inside the relevant test or fixture scope, using narrow route patterns, and closing the context after the test. In parallel execution, mocks, users, data, files, and storage state should be isolated.

Detailed Explanation

A mocked response should not affect another independent test unless the test framework design allows state to leak. In Playwright Java, routes are attached either to a Page or a BrowserContext. If the same context is reused across multiple tests, a route registered in one test can continue affecting later tests.

For example, this kind of broad mock is risky:

browserContext.route("**/api/**", route -> {
    route.fulfill(new Route.FulfillOptions()
        .setStatus(200)
        .setContentType("application/json")
        .setBody("{\"mocked\":true}"));
});

This can accidentally intercept many API calls, not just the endpoint needed for one test. If the same BrowserContext is reused, other tests may receive mocked data when they expect real backend data.

Common causes include:

1. Shared BrowserContext across tests
2. Global route registration in setup
3. Broad route pattern such as **/api/**
4. Mock placed in a base class used by all tests
5. Context not closed after the test
6. Route not removed when the test finishes
7. Parallel tests sharing the same context, user, or storage state
8. Mock intercepting more endpoints than intended

A better design is to scope the mock narrowly inside the test that needs it:

BrowserContext context = browser.newContext();
Page page = context.newPage();

page.route("**/api/orders/summary", route -> {
    route.fulfill(new Route.FulfillOptions()
        .setStatus(200)
        .setContentType("application/json")
        .setBody("{\"totalOrders\":5}"));
});

page.navigate("https://example.com/dashboard");

PlaywrightAssertions.assertThat(
    page.getByText("Total Orders: 5")
).isVisible();

context.close();

If the mock must apply to all pages inside the same test, browserContext.route() is fine, but the context should still be test-scoped:

BrowserContext context = browser.newContext();

context.route("**/api/orders/summary", route -> {
    route.fulfill(new Route.FulfillOptions()
        .setStatus(200)
        .setContentType("application/json")
        .setBody("{\"totalOrders\":5}"));
});

The route pattern should be as specific as possible. Mocking **/api/orders/summary is safer than mocking **/api/** because it limits the mock to the endpoint required by the scenario.

For parallel execution, I would also make sure each test uses isolated users, isolated test data, isolated downloads/uploads, safe storage-state files, and cleanup. A mock leak is often a symptom of a broader isolation problem in the automation framework.

Common mistake: putting all network mocks into global setup for convenience. This makes tests look shorter, but it can cause hidden dependencies, route leakage, false positives, and failures when tests run in a different order or in parallel.


Ready to prepare with the complete 1800+ question ebook?

Buy the Full Ebook