diff --git a/airbyte-webapp/cypress/e2e/connection/syncCatalog.cy.ts b/airbyte-webapp/cypress/e2e/connection/syncCatalog.cy.ts index 8cdaa74726..950e7f5155 100644 --- a/airbyte-webapp/cypress/e2e/connection/syncCatalog.cy.ts +++ b/airbyte-webapp/cypress/e2e/connection/syncCatalog.cy.ts @@ -19,7 +19,9 @@ import { createUsersTableQuery, dropUsersTableQuery, } from "@cy/commands/db/queries"; +import { interceptUpdateConnectionRequest, waitForUpdateConnectionRequest } from "@cy/commands/interceptors"; import { visit } from "@cy/pages/connection/connectionPageObject"; +import { confirmStreamConfigurationChangedPopup } from "@cy/pages/connection/connectionReplicationPageObject"; import { StreamRowPageObjectV2 } from "@cy/pages/connection/StreamRowPageObjectV2"; import { streamsTableV2 } from "@cy/pages/connection/StreamsTablePageObjectV2"; import { setFeatureFlags, setFeatureServiceFlags } from "@cy/support/e2e"; @@ -243,6 +245,283 @@ describe("Stream", { testIsolation: false }, () => { }); }); +describe("Sync Modes", { testIsolation: false }, () => { + let postgresSource: SourceRead; + let postgresDestination: DestinationRead; + let connection: WebBackendConnectionRead; + + before(() => { + setFeatureFlags({ "connection.syncCatalogV2": true }); + setFeatureServiceFlags({ SYNC_CATALOG_V2: true }); + + cleanDBSource(); + runDbQuery(createUsersTableQuery); + runDbQuery(createCitiesTableQuery); + + createPostgresSourceViaApi() + .then((source) => { + postgresSource = source; + }) + .then(() => createPostgresDestinationViaApi()) + .then((destination) => { + postgresDestination = destination; + }) + .then(() => createNewConnectionViaApi(postgresSource, postgresDestination)) + .then((connectionResponse) => { + connection = connectionResponse; + }); + }); + + beforeEach(() => { + interceptUpdateConnectionRequest(); + }); + + after(() => { + setFeatureFlags({}); + setFeatureServiceFlags({}); + + // cleanup + if (postgresSource) { + requestDeleteSource({ sourceId: postgresSource.sourceId }); + } + if (postgresDestination) { + requestDeleteDestination({ + destinationId: postgresDestination.destinationId, + }); + } + if (connection) { + requestDeleteConnection({ connectionId: connection.connectionId }); + } + cleanDBSource(); + }); + + const usersStreamRow = new StreamRowPageObjectV2("public", "users"); + const citiesStreamRow = new StreamRowPageObjectV2("public", "cities"); + + describe("Full refresh | Append", { testIsolation: false }, () => { + it("should select the sync mode", () => { + visit(connection, "replication"); + usersStreamRow.toggleStreamSync(true); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.selectSyncMode(SyncMode.full_refresh, DestinationSyncMode.append); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.append); + }); + + it("should not display the PK and Cursor combobox buttons", () => { + usersStreamRow.isPKComboboxBtnDisplayed(false); + usersStreamRow.isMissedPKErrorDisplayed(false); + usersStreamRow.isCursorComboboxBtnDisplayed(false); + usersStreamRow.isMissedCursorErrorDisplayed(false); + }); + + it("should allow to save changes", () => { + streamsTableV2.isNoStreamsSelectedErrorDisplayed(false); + streamsTableV2.clickSaveChangesButton(); + waitForUpdateConnectionRequest(); + }); + + it("should verify that changes are applied", () => { + visit(connection, "replication"); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.append); + }); + }); + + describe("Full refresh | Overwrite", { testIsolation: false }, () => { + it("should select the sync mode", () => { + visit(connection, "replication"); + usersStreamRow.toggleStreamSync(true); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.selectSyncMode(SyncMode.full_refresh, DestinationSyncMode.overwrite); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.overwrite); + }); + + it("should not display the PK and Cursor combobox buttons", () => { + usersStreamRow.isPKComboboxBtnDisplayed(false); + usersStreamRow.isMissedPKErrorDisplayed(false); + usersStreamRow.isCursorComboboxBtnDisplayed(false); + usersStreamRow.isMissedCursorErrorDisplayed(false); + }); + + it("should allow to save changes", () => { + streamsTableV2.isNoStreamsSelectedErrorDisplayed(false); + streamsTableV2.clickSaveChangesButton(); + waitForUpdateConnectionRequest(); + }); + + it("should verify that changes are applied", () => { + visit(connection, "replication"); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.overwrite); + }); + }); + + describe("Full refresh | Overwrite + Deduped", { testIsolation: false }, () => { + it("should select the sync mode", () => { + visit(connection, "replication"); + citiesStreamRow.toggleStreamSync(true); + citiesStreamRow.isStreamSyncEnabled(true); + citiesStreamRow.selectSyncMode(SyncMode.full_refresh, DestinationSyncMode.overwrite_dedup); + citiesStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.overwrite_dedup); + }); + + it("should show missing PK error", () => { + citiesStreamRow.isMissedPKErrorDisplayed(true); + citiesStreamRow.isMissedCursorErrorDisplayed(false); + streamsTableV2.isSaveChangesButtonEnabled(false); + }); + + it("should select PK", () => { + citiesStreamRow.selectPKs(["city_code"]); + citiesStreamRow.isSelectedPKDisplayed("city_code"); + citiesStreamRow.isMissedPKErrorDisplayed(false); + citiesStreamRow.toggleExpandCollapseStream(); + citiesStreamRow.isPKField("city_code", true); + }); + + it("should allow to save changes", () => { + streamsTableV2.clickSaveChangesButton(); + waitForUpdateConnectionRequest(); + }); + + it("should select multiple PKs", () => { + visit(connection, "replication"); + citiesStreamRow.selectPKs(["city"]); + citiesStreamRow.isSelectedPKDisplayed("2 items selected"); + citiesStreamRow.toggleExpandCollapseStream(); + citiesStreamRow.isPKField("city_code", true); + citiesStreamRow.isPKField("city", true); + streamsTableV2.clickSaveChangesButton(); + waitForUpdateConnectionRequest(); + }); + + it("should verify that changes are applied", () => { + visit(connection, "replication"); + citiesStreamRow.isStreamSyncEnabled(true); + citiesStreamRow.isSelectedPKDisplayed("2 items selected"); + citiesStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.overwrite); + }); + }); + + describe("Full refresh | Overwrite + Deduped (source-defined PK)", { testIsolation: false }, () => { + it("should select the sync mode", () => { + visit(connection, "replication"); + usersStreamRow.toggleStreamSync(true); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.selectSyncMode(SyncMode.full_refresh, DestinationSyncMode.overwrite_dedup); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.overwrite_dedup); + }); + + it("should NOT show missing PK and Cursor error", () => { + usersStreamRow.isMissedPKErrorDisplayed(false); + usersStreamRow.isMissedCursorErrorDisplayed(false); + streamsTableV2.isSaveChangesButtonEnabled(true); + }); + + it("should show non-editable selected PK", () => { + usersStreamRow.isSelectedPKDisplayed("id"); + usersStreamRow.isPKComboboxBtnDisabled(true); + usersStreamRow.toggleExpandCollapseStream(); + usersStreamRow.isPKField("id", true); + }); + + it("should allow to save changes", () => { + streamsTableV2.clickSaveChangesButton(); + waitForUpdateConnectionRequest(); + }); + + it("should verify that changes are applied", () => { + visit(connection, "replication"); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.isSelectedPKDisplayed("id"); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.full_refresh, DestinationSyncMode.overwrite); + }); + }); + + describe("Incremental | Append", { testIsolation: false }, () => { + it("should select the sync mode", () => { + visit(connection, "replication"); + usersStreamRow.toggleStreamSync(true); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.selectSyncMode(SyncMode.incremental, DestinationSyncMode.append); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.incremental, DestinationSyncMode.append); + }); + + it("should show missing Cursor error", () => { + usersStreamRow.isMissedPKErrorDisplayed(false); + usersStreamRow.isMissedCursorErrorDisplayed(true); + streamsTableV2.isSaveChangesButtonEnabled(false); + }); + + it("should select Cursor", () => { + usersStreamRow.selectCursor("email"); + usersStreamRow.isSelectedCursorDisplayed("email"); + usersStreamRow.toggleExpandCollapseStream(); + usersStreamRow.isCursorField("email", true); + }); + + it("should allow to save changes", () => { + streamsTableV2.clickSaveChangesButton(); + waitForUpdateConnectionRequest(); + }); + + it("should verify that changes are applied", () => { + visit(connection, "replication"); + usersStreamRow.isStreamSyncEnabled(true); + usersStreamRow.isSelectedCursorDisplayed("email"); + usersStreamRow.isSelectedSyncModeDisplayed(SyncMode.incremental, DestinationSyncMode.append); + }); + }); + + describe("Incremental | Append + Deduped", { testIsolation: false }, () => { + it("should select the sync mode", () => { + // trick to unset PK from previous tests + visit(connection, "replication"); + citiesStreamRow.selectPKs(["city_code", "city"]); + citiesStreamRow.selectSyncMode(SyncMode.full_refresh, DestinationSyncMode.overwrite); + streamsTableV2.clickSaveChangesButton(); + waitForUpdateConnectionRequest(); + + visit(connection, "replication"); + citiesStreamRow.toggleStreamSync(true); + citiesStreamRow.isStreamSyncEnabled(true); + + citiesStreamRow.selectSyncMode(SyncMode.incremental, DestinationSyncMode.append_dedup); + citiesStreamRow.isSelectedSyncModeDisplayed(SyncMode.incremental, DestinationSyncMode.append_dedup); + }); + + it("should show missing PK and Cursor errors", () => { + citiesStreamRow.isMissedPKErrorDisplayed(true); + citiesStreamRow.isMissedCursorErrorDisplayed(true); + streamsTableV2.isSaveChangesButtonEnabled(false); + }); + + it("should select PK and Cursor", () => { + citiesStreamRow.selectPKs(["city_code"]); + citiesStreamRow.isSelectedPKDisplayed("city_code"); + citiesStreamRow.selectCursor("city"); + citiesStreamRow.isSelectedCursorDisplayed("city"); + citiesStreamRow.toggleExpandCollapseStream(); + citiesStreamRow.isPKField("city_code", true); + citiesStreamRow.isCursorField("city", true); + }); + + it("should allow to save changes and discard refresh streams", () => { + streamsTableV2.clickSaveChangesButton(); + confirmStreamConfigurationChangedPopup({ reset: false }); + waitForUpdateConnectionRequest(); + }); + + it("should verify that changes are applied", () => { + visit(connection, "replication"); + citiesStreamRow.isStreamSyncEnabled(true); + citiesStreamRow.isSelectedPKDisplayed("city_code"); + citiesStreamRow.isSelectedCursorDisplayed("city"); + citiesStreamRow.isSelectedSyncModeDisplayed(SyncMode.incremental, DestinationSyncMode.append_dedup); + }); + }); +}); + describe("Sync Catalog - deleted connection", { testIsolation: false }, () => { let postgresSource: SourceRead; let postgresDestination: DestinationRead; diff --git a/airbyte-webapp/cypress/pages/connection/StreamRowPageObjectV2.ts b/airbyte-webapp/cypress/pages/connection/StreamRowPageObjectV2.ts index 258a8c3f7b..8c7193dd25 100644 --- a/airbyte-webapp/cypress/pages/connection/StreamRowPageObjectV2.ts +++ b/airbyte-webapp/cypress/pages/connection/StreamRowPageObjectV2.ts @@ -100,7 +100,7 @@ export class StreamRowPageObjectV2 { const syncMode = `${SYNC_MODE_STRINGS[source]} | ${SYNC_MODE_STRINGS[dest]}`; this.withinStream(() => { - cy.get(streamSyncModeSelectButton).click({ force: true }); + cy.get(streamSyncModeSelectButton).click(); cy.get('li[role="option"]') // It's possible that there are multiple options with the same text, so we need to filter by exact text content // instead of using .contains(), e.g. "Incremental | Append" and "Incremental | Append + Dedupe" @@ -132,12 +132,16 @@ export class StreamRowPageObjectV2 { cy.contains(pk).click(); }); }); + // Press ESC key to close the dropdown + cy.get(streamPKCell).type("{esc}"); }); } isMissedPKErrorDisplayed(expectedResult: boolean) { this.withinStream(() => { - cy.contains("Primary key missing").should(expectedResult ? "be.visible" : "not.be.visible"); + cy.get(streamPKCell) + .contains("Primary key missing") + .should(expectedResult ? "exist" : "not.exist"); }); } @@ -175,7 +179,9 @@ export class StreamRowPageObjectV2 { isMissedCursorErrorDisplayed(expectedResult: boolean) { this.withinStream(() => { - cy.contains("Cursor missing").should(expectedResult ? "be.visible" : "not.be.visible"); + cy.get(streamCursorCell) + .contains("Cursor missing") + .should(expectedResult ? "exist" : "not.exist"); }); } @@ -195,6 +201,12 @@ export class StreamRowPageObjectV2 { }); } + isSelectedCursorDisplayed(expectedValue: string) { + this.withinStream(() => { + cy.get(streamCursorCell).should("have.text", expectedValue); + }); + } + // Fields toggleFieldSync(fieldName: string, enabled: boolean) { this.withinField(fieldName, () => { diff --git a/airbyte-webapp/cypress/pages/connection/StreamsTablePageObjectV2.ts b/airbyte-webapp/cypress/pages/connection/StreamsTablePageObjectV2.ts index 57f1b4cc8f..f446cfaaf4 100644 --- a/airbyte-webapp/cypress/pages/connection/StreamsTablePageObjectV2.ts +++ b/airbyte-webapp/cypress/pages/connection/StreamsTablePageObjectV2.ts @@ -5,6 +5,8 @@ const streamNameInput = "input[data-testid='sync-catalog-search']"; const syncNamespaceCheckbox = "input[data-testid='sync-namespace-checkbox']"; // table controls +const discardChangesButton = "button[data-testid='cancel-edit-button']"; +const saveChangesButton = "button[data-testid='save-edit-button']"; const refreshSchemaButton = "button[data-testid='refresh-schema-btn']"; const expandCollapseAllStreamsButton = "button[data-testid='expand-collapse-all-streams-btn']"; @@ -170,6 +172,19 @@ export class StreamsTablePageObjectV2 { isNoStreamsSelectedErrorDisplayed(expectedResult: boolean) { cy.contains(noStreamsSelectedError).should(expectedResult ? "exist" : "not.exist"); } + + // form controls + clickDiscardChangesButton() { + cy.get(discardChangesButton).click(); + } + + clickSaveChangesButton() { + cy.get(saveChangesButton).click(); + } + + isSaveChangesButtonEnabled(expectedResult: boolean) { + cy.get(saveChangesButton).should(expectedResult ? "be.enabled" : "be.disabled"); + } } export const streamsTableV2 = new StreamsTablePageObjectV2();