-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Improve E2E test stability for Capacitor and Next.js component selection #2646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import { expect } from "@playwright/test"; | ||
| import { testSkipIfWindows, Timeout } from "./helpers/test_helper"; | ||
|
|
||
| testSkipIfWindows("capacitor upgrade and sync works", async ({ po }) => { | ||
|
|
@@ -10,25 +11,59 @@ testSkipIfWindows("capacitor upgrade and sync works", async ({ po }) => { | |
|
|
||
| await po.page.getByTestId("capacitor-controls").waitFor({ state: "visible" }); | ||
|
|
||
| // Helper to wait for sync operation to complete and dismiss error dialog if it appears | ||
| // The sync operation may fail in E2E environment due to missing CocoaPods/Xcode | ||
| const waitForSyncCompletionAndDismissErrorIfNeeded = async ( | ||
| buttonText: string, | ||
| ) => { | ||
| // Wait for either the button to return to idle state OR an error dialog to appear | ||
| const idleButton = po.page.getByRole("button", { | ||
| name: new RegExp(buttonText, "i"), | ||
| }); | ||
| const errorDialog = po.page.getByRole("dialog"); | ||
|
|
||
| // Use Promise.race to wait for either condition | ||
| await expect(async () => { | ||
| const isButtonEnabled = | ||
| (await idleButton.isVisible()) && | ||
| !(await idleButton.isDisabled()) && | ||
| (await idleButton.textContent())?.includes(buttonText); | ||
| const isErrorDialogVisible = await errorDialog.isVisible(); | ||
| expect(isButtonEnabled || isErrorDialogVisible).toBe(true); | ||
| }).toPass({ timeout: Timeout.EXTRA_LONG }); | ||
|
|
||
| // If error dialog appeared, dismiss it | ||
| if (await errorDialog.isVisible()) { | ||
| // Click the Close button within the dialog | ||
| await errorDialog.getByRole("button", { name: "Close" }).first().click(); | ||
| // Wait for dialog to close | ||
| await expect(errorDialog).toBeHidden({ timeout: Timeout.SHORT }); | ||
| } | ||
| }; | ||
|
Comment on lines
+16
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This helper function is a great addition for test stability. It can be simplified by using Playwright's locator const waitForSyncCompletionAndDismissErrorIfNeeded = async (
buttonText: string,
) => {
// Use a locator that waits for the button to be enabled.
const idleButton = po.page.getByRole("button", {
name: new RegExp(buttonText, "i"),
disabled: false,
});
const errorDialog = po.page.getByRole("dialog");
// Use .or() to wait for either the idle button or the error dialog to be visible.
await expect(idleButton.or(errorDialog).first()).toBeVisible({
timeout: Timeout.EXTRA_LONG,
});
// If error dialog appeared, dismiss it.
if (await errorDialog.isVisible()) {
// Click the Close button within the dialog.
await errorDialog.getByRole("button", { name: "Close" }).first().click();
// Wait for dialog to close.
await expect(errorDialog).toBeHidden({ timeout: Timeout.SHORT });
}
}; |
||
|
|
||
| // Test sync & open iOS functionality - the button contains "Sync & Open iOS" | ||
| const iosButton = po.page.getByRole("button", { name: /Sync & Open iOS/i }); | ||
| await iosButton.click(); | ||
|
|
||
| // In test mode, this should complete without error and return to idle state | ||
| // Wait for the button to be enabled again (not in loading state) | ||
| await po.page | ||
| .getByText("Sync & Open iOS") | ||
| .waitFor({ state: "visible", timeout: Timeout.LONG }); | ||
| // Wait for sync operation to complete and dismiss error dialog if needed | ||
| await waitForSyncCompletionAndDismissErrorIfNeeded("Sync & Open iOS"); | ||
|
|
||
| // Verify the button is back to idle state | ||
| await expect( | ||
| po.page.getByRole("button", { name: /Sync & Open iOS/i }), | ||
| ).toBeVisible({ timeout: Timeout.MEDIUM }); | ||
|
Comment on lines
+52
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check await expect(
po.page.getByRole("button", { name: /Sync & Open iOS/i }),
).toBeEnabled({ timeout: Timeout.MEDIUM }); |
||
|
|
||
| // Test sync & open Android functionality - the button contains "Sync & Open Android" | ||
| const androidButton = po.page.getByRole("button", { | ||
| name: /Sync & Open Android/i, | ||
| }); | ||
| await androidButton.click(); | ||
|
|
||
| // In test mode, this should complete without error and return to idle state | ||
| // Wait for the button to be enabled again (not in loading state) | ||
| await po.page | ||
| .getByText("Sync & Open Android") | ||
| .waitFor({ state: "visible", timeout: Timeout.LONG }); | ||
| // Wait for sync operation to complete and dismiss error dialog if needed | ||
| await waitForSyncCompletionAndDismissErrorIfNeeded("Sync & Open Android"); | ||
|
|
||
| // Verify the button is back to idle state | ||
| await expect( | ||
| po.page.getByRole("button", { name: /Sync & Open Android/i }), | ||
| ).toBeVisible({ timeout: Timeout.MEDIUM }); | ||
|
Comment on lines
+66
to
+68
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the iOS button check, await expect(
po.page.getByRole("button", { name: /Sync & Open Android/i }),
).toBeEnabled({ timeout: Timeout.MEDIUM }); |
||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unscoped dialog dismissal
const errorDialog = po.page.getByRole("dialog")matches any dialog on the page, so this helper can end up clicking aClosebutton in an unrelated modal (consent/settings/etc.) and still proceed, masking the real sync state and introducing flakiness. Please scope the locator to the specific sync error dialog (e.g., by accessible name/title/text unique to that modal) before dismissing it.Prompt To Fix With AI