Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/webview/aria-expanded-matcher.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Coverage for the shared `toBeExpanded()` matcher defined in tests/e2e/matchers.ts. It lives here,
// not in Electron E2E, because the rollwright functional harness is the only non-Electron way to run
// real-DOM Playwright assertions, keeping the `.not` and absent-attribute edge cases fast in CI.
import { expect } from "@playwright/test";
// registers the `toBeExpanded` matcher on Playwright's shared `expect`
import "../../tests/e2e/matchers";
import { test } from "./baseTest";

// minimal tree items covering the three aria-expanded states the matcher distinguishes
const TREE_ITEMS = `
<div role="treeitem" data-testid="expanded" aria-expanded="true">expanded</div>
<div role="treeitem" data-testid="collapsed" aria-expanded="false">collapsed</div>
<div role="treeitem" data-testid="leaf">leaf</div>
`;

test.use({ coverage: false });

test.beforeEach(async ({ page }) => {
await page.setContent(TREE_ITEMS);
});

test('toBeExpanded() passes for aria-expanded="true"', async ({ page }) => {
await expect(page.getByTestId("expanded")).toBeExpanded();
});

test('not.toBeExpanded() passes for aria-expanded="false"', async ({ page }) => {
await expect(page.getByTestId("collapsed")).not.toBeExpanded();
});

test("not.toBeExpanded() passes when aria-expanded is absent", async ({ page }) => {
await expect(page.getByTestId("leaf")).not.toBeExpanded();
});
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ To create a new E2E test:
test.beforeEach(async ({ connectionItem }) => {
// The connectionItem fixture ensures the connection is set up and expanded in the Resources
// view, and handles teardown automatically after the test completes.
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();
});

test("should open message viewer from a topic", async ({ page }) => {
Expand Down
6 changes: 4 additions & 2 deletions tests/e2e/baseTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { tmpdir } from "os";
import path from "path";
import { fileURLToPath } from "url";
import { DEBUG_LOGGING_ENABLED } from "./constants";
// registers the `toBeExpanded` custom matcher on Playwright's shared `expect`
import "./matchers";
import { NotificationArea } from "./objects/notifications/NotificationArea";
import { Quickpick } from "./objects/quickInputs/Quickpick";
import { FlinkDatabaseView, SelectFlinkDatabase } from "./objects/views/FlinkDatabaseView";
Expand Down Expand Up @@ -305,7 +307,7 @@ export const test = testBase.extend<VSCodeFixtures>({
}

// ensure connection has resources available to work with
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();

const topicName = e2eResourceName(topicConfig.name);

Expand Down Expand Up @@ -361,7 +363,7 @@ export const test = testBase.extend<VSCodeFixtures>({
throw new Error("artifactConfig must be set, like `test.use({ artifactConfig: {} })`");
}

await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();

const entrypoint = artifactConfig.entrypoint ?? SelectFlinkDatabase.FromDatabaseViewButton;
const jarPath =
Expand Down
58 changes: 58 additions & 0 deletions tests/e2e/matchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { ExpectMatcherState, Locator, MatcherReturnType } from "@playwright/test";
import { expect } from "@playwright/test";

/** Options accepted by {@linkcode toBeExpanded}, mirroring built-in locator assertions. */
interface ToBeExpandedOptions {
timeout?: number;
}

/**
* Assert that a tree item or view header is expanded, i.e. carries `aria-expanded="true"`.
*
* Delegates to the auto-retrying `toHaveAttribute` assertion, so it preserves web-first
* auto-waiting and works with `.not`.
*/
async function toBeExpanded(
this: ExpectMatcherState,
locator: Locator,
options?: ToBeExpandedOptions,
): Promise<MatcherReturnType> {
const assertionName = "toBeExpanded";
let pass: boolean;
let matcherResult: { actual?: unknown } | undefined;
try {
// delegate through `.not` when negated so auto-waiting polls in the expected direction
const assertion = this.isNot ? expect(locator).not : expect(locator);
await assertion.toHaveAttribute("aria-expanded", "true", options);
Comment thread
shouples marked this conversation as resolved.
pass = true;
} catch (error: unknown) {
matcherResult = (error as { matcherResult?: { actual?: unknown } }).matcherResult;
pass = false;
}
// `.not` was already applied above; flip back so the returned `pass` reflects the base assertion.
if (this.isNot) {
pass = !pass;
}

const message = () => {
const hint = this.utils.matcherHint(assertionName, undefined, "", { isNot: this.isNot });
return (
`${hint}\n\n` +
`Expected: ${this.isNot ? "not " : ""}aria-expanded="true"\n` +
`Received: aria-expanded=${this.utils.printReceived(matcherResult?.actual)}`
);
};

return { message, pass, name: assertionName, expected: "true", actual: matcherResult?.actual };
}

expect.extend({ toBeExpanded });

declare global {
namespace PlaywrightTest {
interface Matchers<R, T = unknown> {
/** Assert the locator carries `aria-expanded="true"` (i.e. the tree item/view is expanded). */
toBeExpanded(options?: ToBeExpandedOptions): R;
}
}
}
2 changes: 1 addition & 1 deletion tests/e2e/objects/views/FlinkDatabaseView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ export class FlinkDatabaseView extends SearchableView {
// containers are always Collapsed by default, so we don't need to check for null here
if (isExpanded === "false") {
await container.click();
await expect(container).toHaveAttribute("aria-expanded", "true");
await expect(container).toBeExpanded();
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/objects/views/ResourcesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export class ResourcesView extends SearchableView {
if ((await environment.getAttribute("aria-expanded")) === "false") {
await environment.click();
}
await expect(environment).toHaveAttribute("aria-expanded", "true");
await expect(environment).toBeExpanded();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/objects/views/SchemasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class SchemasView extends SearchableView {
default:
throw new Error(`Unsupported entrypoint: ${entrypoint}`);
}
await expect(this.header).toHaveAttribute("aria-expanded", "true");
await expect(this.header).toBeExpanded();
await expect(this.body).toBeVisible();
}

Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/objects/views/TopicsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export class TopicsView extends SearchableView {
default:
throw new Error(`Unsupported entrypoint: ${entrypoint}`);
}
await expect(this.header).toHaveAttribute("aria-expanded", "true");
await expect(this.header).toBeExpanded();
await expect(this.body).toBeVisible();
await expect(this.progressIndicator).toBeHidden();
await this.waitForContainerLoaded(this.topicsContainer);
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/objects/views/View.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class View {
if (isExpanded !== "true") {
await this.header.click();
}
await expect(this.header).toHaveAttribute("aria-expanded", "true");
await expect(this.header).toBeExpanded();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/specs/directConnectionLifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ test.describe("Direct Connection CRUD Lifecycle", { tag: [Tag.DirectConnectionCR

test.beforeEach(async ({ electronApp, page, connectionItem }) => {
// make sure the local item is expanded before we try to copy local resources properties
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();

// grant clipboard access to read the copied bootstrap servers and schema registry URL
// from the local items' context menu actions
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/specs/flinkArtifact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const __dirname = path.dirname(__filename);
test.describe("Flink Artifacts", { tag: [Tag.CCloud, Tag.FlinkArtifacts] }, () => {
test.use({ connectionType: ConnectionType.Ccloud });
test.beforeEach(async ({ connectionItem }) => {
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();
});

test.afterEach(async ({ page }) => {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/specs/flinkStatement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ test.describe("Flink Statements", { tag: [Tag.CCloud, Tag.FlinkStatements] }, ()

test.beforeEach(async ({ connectionItem }) => {
// ensure connection tree item has resources available to work with
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();
webview = undefined;
statementName = undefined;
});
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/specs/produceMessage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ test.describe("Produce Message(s) to Topic", { tag: [Tag.ProduceMessageToTopic]

test.beforeEach(async ({ page, connectionItem, topic: topicName }) => {
// ensure connection tree item has resources available to work with
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();

const topicsView = new TopicsView(page);
// click a Kafka cluster from the Resources view to open and populate the Topics view
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/specs/scaffold.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ test.describe("Project Scaffolding", { tag: [Tag.ProjectScaffolding] }, () => {

test.beforeEach(async ({ connectionItem }) => {
// ensure connection tree item has resources available to work with
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();
});

test(`should apply Flink Table API In Java For Confluent Cloud template from Flink compute pool`, async ({
Expand Down Expand Up @@ -163,7 +163,7 @@ test.describe("Project Scaffolding", { tag: [Tag.ProjectScaffolding] }, () => {

test.beforeEach(async ({ connectionItem }) => {
// ensure connection tree item has resources available to work with
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();
});

test(
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/specs/schemas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ test.describe("Schema Management", { tag: [Tag.EvolveSchema] }, () => {

test.beforeEach(async ({ page, connectionItem }) => {
// ensure connection tree item has resources available to work with
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();

const schemasView = new SchemasView(page);
await schemasView.loadSchemaSubjects(
Expand Down
8 changes: 4 additions & 4 deletions tests/e2e/utils/connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export async function setupCCloudConnection(
}

await expect(ccloudItem.locator).not.toContainText(NOT_CONNECTED_TEXT);
await expect(ccloudItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(ccloudItem.locator).toBeExpanded();
return ccloudItem;
}

Expand Down Expand Up @@ -229,7 +229,7 @@ export async function setupDirectConnection(
if ((await connectionItem.locator.getAttribute("aria-expanded")) === "false") {
await connectionItem.locator.click();
}
await expect(connectionItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(connectionItem.locator).toBeExpanded();
}

return connectionItem;
Expand Down Expand Up @@ -299,7 +299,7 @@ export async function setupLocalKafka(page: Page) {
// broker input did not appear within 5s — continue without confirming
}

await expect(localItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(localItem.locator).toBeExpanded();
await expect(resourcesView.localKafkaClusters).not.toHaveCount(0);
return localItem;
}
Expand All @@ -317,7 +317,7 @@ export async function setupLocalSchemaRegistry(page: Page) {
await containerQuickpick.selectItemByText("Schema Registry");
await containerQuickpick.confirm();

await expect(localItem.locator).toHaveAttribute("aria-expanded", "true");
await expect(localItem.locator).toBeExpanded();
// local SR requires local Kafka, so we should always see local Kafka appear
await expect(resourcesView.localKafkaClusters).not.toHaveCount(0);
await expect(resourcesView.localSchemaRegistries).not.toHaveCount(0, { timeout: 60_000 });
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/utils/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export async function openConfluentSidebar(page: Page): Promise<void> {

const resourcesView = new ResourcesView(page);
// the Resources should be visible and expanded by default
await expect(resourcesView.header).toHaveAttribute("aria-expanded", "true");
await expect(resourcesView.header).toBeExpanded();
// and should show the "Confluent Cloud" placeholder item (not "No resources found")
await expect(resourcesView.confluentCloudItem).toBeVisible();
// we don't check for the "Local" item here in the event the Confluent Cloud item has children
Expand Down