diff --git a/packages/insomnia-test/components/preferences-credentials.component.ts b/packages/insomnia-test/components/preferences-credentials.component.ts index 964f9194f8..1c13b1c194 100644 --- a/packages/insomnia-test/components/preferences-credentials.component.ts +++ b/packages/insomnia-test/components/preferences-credentials.component.ts @@ -27,6 +27,20 @@ export class PreferencesCredentialsComponent extends PreferencesTabComponent { await this.tabs.tabByName("Credentials").click(); } + private createGitCredentialMenu(): DropdownComponent { + return new DropdownComponent( + this.page, + this.dialog.getByRole("button", { name: "Create Git Credential" }), + ); + } + + private createCloudCredentialMenu(): DropdownComponent { + return new DropdownComponent( + this.page, + this.dialog.getByRole("button", { name: "Create Cloud Credential" }), + ); + } + /** * Adds a custom (username/PAT) Git credential from the Credentials tab, * which must already be open (see `openCredentialsTab()`). @@ -38,9 +52,7 @@ export class PreferencesCredentialsComponent extends PreferencesTabComponent { name: /Access Token Credential/, }), ); - await DropdownComponent.byLabel(this.page, "Create Git Credential").select( - "Access Token", - ); + await this.createGitCredentialMenu().select("Access Token"); await this.page.getByLabel("Author Email").fill(credential.authorEmail); await this.page.getByLabel("Author Name").fill(credential.authorName); await this.page.getByLabel("Username").fill(credential.username); @@ -63,10 +75,7 @@ export class PreferencesCredentialsComponent extends PreferencesTabComponent { async createAwsCloudCredential( credential: Omit, ): Promise { - await new DropdownComponent( - this.page, - this.dialog.getByRole("button", { name: "Create Cloud Credential" }), - ).select("AWS", false); + await this.createCloudCredentialMenu().select("AWS", false); const modal = new ModalComponent( this.page, this.page.getByRole("dialog", { @@ -98,10 +107,7 @@ export class PreferencesCredentialsComponent extends PreferencesTabComponent { async createGcpCloudCredential( credential: Omit, ): Promise { - await new DropdownComponent( - this.page, - this.dialog.getByRole("button", { name: "Create Cloud Credential" }), - ).select("GCP", false); + await this.createCloudCredentialMenu().select("GCP", false); const modal = new ModalComponent( this.page, this.page.getByRole("dialog", { @@ -131,10 +137,7 @@ export class PreferencesCredentialsComponent extends PreferencesTabComponent { async createHashiCorpCloudCredential( credential: Omit, ): Promise { - await new DropdownComponent( - this.page, - this.dialog.getByRole("button", { name: "Create Cloud Credential" }), - ).select("HashiCorp", false); + await this.createCloudCredentialMenu().select("HashiCorp", false); const modal = new ModalComponent( this.page, this.page.getByRole("dialog", { diff --git a/packages/insomnia-test/pages/spec.page.ts b/packages/insomnia-test/pages/spec.page.ts index 15d653c685..e6d52dd0e2 100644 --- a/packages/insomnia-test/pages/spec.page.ts +++ b/packages/insomnia-test/pages/spec.page.ts @@ -245,12 +245,15 @@ export class SpecPage extends BasePage { * all. */ async getOpenApiVersion(): Promise { - const label = this.page - .locator(this.PANE_ONE) - .first() - .getByText(/OpenAPI \d/) - .first(); - if ((await label.count()) === 0) return ""; + // The span is always rendered once the spec has content, but stays + // empty until the parsed spec's `openapi` field is known, so wait for + // text rather than for the element. + const label = this.page.getByTestId("spec-openapi-version"); + try { + await expect(label).toHaveText(/OpenAPI \d/, { timeout: DEFAULT_TIMEOUT }); + } catch { + return ""; + } return (await label.innerText()).trim(); } @@ -379,10 +382,14 @@ export class SpecPage extends BasePage { * Reads every suite name currently listed in the Test Suites sidebar. */ async getTestSuiteNames(): Promise { - return new TableComponent( - this.page, - this.page.locator(this.TEST_SUITES_LIST), - ).rows.allInnerTexts(); + // Re-opening the Tests tab re-renders the list; wait for it to settle + // so a read taken mid-render doesn't return a stale or partial list. + return this.waitForStableValue(() => + new TableComponent( + this.page, + this.page.locator(this.TEST_SUITES_LIST), + ).rows.allInnerTexts(), + ); } /** diff --git a/packages/insomnia-test/pages/workspace.page.ts b/packages/insomnia-test/pages/workspace.page.ts index 45ca7bdb45..654e6b8ce0 100644 --- a/packages/insomnia-test/pages/workspace.page.ts +++ b/packages/insomnia-test/pages/workspace.page.ts @@ -1144,18 +1144,20 @@ export class WorkspacePage extends BasePage { const target = indicator.first(); const folderBadge = target.getByTestId("drop-indicator-folder-name"); + // The sidebar tags its indicator marker (the "on" wrapper, folder-name + // badge or dot) with `data-valid` and `data-drop-position`. An invalid + // target may render without the marker or the aria label, so only read + // what is actually present rather than letting getAttribute() wait out + // the full action timeout. + const marker = target.locator("[data-drop-position]").first(); + const dropLabel = target.locator('[aria-roledescription="drop indicator"]'); + const hasMarker = (await marker.count()) > 0; const [ariaLabel, folderName] = await Promise.all([ - target - .locator('[aria-roledescription="drop indicator"]') - .getAttribute("aria-label"), + (await dropLabel.count()) > 0 ? dropLabel.first().getAttribute("aria-label") : null, (await folderBadge.count()) > 0 ? folderBadge.innerText() : "", ]); - // react-aria labels an "on" drop "Drop on "; line drops say - // "Insert ...". - const isValid = - (await target.locator("[data-valid]").first().getAttribute("data-valid")) === - "true"; - const isInto = (ariaLabel ?? "").startsWith("Drop on"); + const isValid = hasMarker && (await marker.getAttribute("data-valid")) === "true"; + const isInto = hasMarker && (await marker.getAttribute("data-drop-position")) === "on"; const between = ariaLabel?.match(/^Insert between (.+) and (.+)$/); return { diff --git a/packages/insomnia-test/tests/workspace/rename-and-delete-project.spec.ts b/packages/insomnia-test/tests/workspace/rename-and-delete-project.spec.ts index 60623ec3d3..1f4ceb556f 100644 --- a/packages/insomnia-test/tests/workspace/rename-and-delete-project.spec.ts +++ b/packages/insomnia-test/tests/workspace/rename-and-delete-project.spec.ts @@ -23,7 +23,5 @@ test("Verify Renaming And Deleting A Project Via The Dashboard", async ({ expect(foundByNewName?.id).toBe(project.id); expect(foundByOldName).toBeUndefined(); - await expect - .poll(() => workspaceFlow.getProject(newName), { timeout: 10_000 }) - .toBeUndefined(); + expect(await workspaceFlow.getProject(newName)).toBeUndefined(); }); diff --git a/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/use-sidebar-drag-and-drop.tsx b/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/use-sidebar-drag-and-drop.tsx index 0f3a2cbcbf..cfac925990 100644 --- a/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/use-sidebar-drag-and-drop.tsx +++ b/packages/insomnia/src/ui/components/sidebar/project-navigation-sidebar/use-sidebar-drag-and-drop.tsx @@ -535,10 +535,17 @@ function SidebarDropIndicator({ className={`absolute right-1 left-1 z-10 flex items-center rounded-sm outline-2 outline-solid ${outlineClass}`} style={{ transform: `translateY(${rowStart}px)`, height: `${rowHeight}px` }} > - + + + ); } @@ -557,6 +564,7 @@ function SidebarDropIndicator({ - + {parsedSpec?.openapi ? `OpenAPI ${parsedSpec.openapi}` : ''}