Skip to content

Commit

Permalink
🔧 Refactor connector builder E2E tests to be less flaky and more retr…
Browse files Browse the repository at this point in the history
…yable (#11015)

Co-authored-by: Alex Birdsall <[email protected]>
  • Loading branch information
lmossman and ambirdsall committed Feb 1, 2024
1 parent deb4f6e commit ab28dab
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 74 deletions.
4 changes: 0 additions & 4 deletions airbyte-webapp/cypress/commands/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ export const createTestConnection = (sourceName: string, destinationName: string
createLocalJsonDestination(destinationName);
}

// TODO is this actually needed?
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(5000);

cy.get("a[data-testid='connections-step']").click();
openCreateConnection();

Expand Down
35 changes: 16 additions & 19 deletions airbyte-webapp/cypress/commands/connectorBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ import {
assertHasNumberOfPages,
configureLimitOffsetPagination,
configureParameters,
disableAutoImportSchema,
disablePagination,
disableStreamSlicer,
enablePagination,
enableParameterizedRequests,
enterName,
Expand Down Expand Up @@ -38,6 +35,8 @@ export const configureGlobals = (name: string) => {
} else {
enterUrlBase("http://172.17.0.1:6767/");
}

configureAuth();
};

export const configureStream = () => {
Expand All @@ -46,7 +45,6 @@ export const configureStream = () => {
enterUrlPathFromForm("items/");
submitForm();
enterRecordSelector("items");
disableAutoImportSchema();
};

export const configureAuth = () => {
Expand All @@ -55,7 +53,6 @@ export const configureAuth = () => {
openTestInputs();
enterTestInputs({ apiKey: "theauthkey" });
submitForm();
goToView("0");
};

export const configurePagination = () => {
Expand All @@ -71,18 +68,11 @@ export const configureParameterizedRequests = (numberOfParameters: number) => {
enterUrlPath("items/{{}{{} stream_slice.item_id }}");
};

export const cleanUp = () => {
goToView("0");
cy.get('[data-testid="tag-tab-stream-configuration"]').click({ force: true });
disablePagination();
disableStreamSlicer();
};

export const publishProject = () => {
// debounce is 2500 so we need to wait at least more before change page
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(30000);
cy.get('[data-testid="publish-button"]').click({ force: true });
cy.wait(3000);
cy.get('[data-testid="publish-button"]').click();
submitForm();
};

Expand Down Expand Up @@ -159,11 +149,18 @@ const SCHEMA_WITH_MISMATCH =
'{{}"$schema": "http://json-schema.org/schema#", "properties": {{}"name": {{}"type": "number"}}, "type": "object"}';
export const acceptSchemaWithMismatch = () => {
openStreamSchemaTab();
cy.get("textarea").clear({ force: true });
// TODO is this actually needed?
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get("textarea").type(SCHEMA_WITH_MISMATCH, { force: true });
// When running against local dev webapp, some uncaught exceptions may be thrown when the monaco editor is loaded.
// Ignore them since they do not affect the test.
cy.on("uncaught:exception", (err) => {
const monacoLoadCancelled = (err as { msg?: string })?.msg?.includes("operation is manually canceled");
const monacoScriptLoadFailed = err?.message?.includes("importScripts") && err?.message?.includes("monaco-editor");
if (monacoLoadCancelled || monacoScriptLoadFailed) {
return false;
}
});
cy.get("label").contains("Automatically import detected schema").click();
cy.get("textarea").clear();
cy.get("textarea").type(SCHEMA_WITH_MISMATCH);
};

export const assertSchemaMismatch = () => {
Expand Down
27 changes: 8 additions & 19 deletions airbyte-webapp/cypress/e2e/connectorBuilder.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import {
assertTestReadAuthFailure,
assertTestReadItems,
assertUrlPath,
cleanUp,
configureAuth,
configureGlobals,
configurePagination,
configureParameterizedRequests,
Expand All @@ -30,14 +28,16 @@ import {
goToConnectorBuilderProjectsPage,
goToView,
selectActiveVersion,
selectAuthMethod,
startFromScratch,
testStream,
} from "pages/connectorBuilderPage";
import { goToSourcePage, openSourceConnectionsPage } from "pages/sourcePage";

describe("Connector builder", { testIsolation: false }, () => {
const connectorName = appendRandomString("dummy_api");
before(() => {
let connectorName = "";
beforeEach(() => {
connectorName = appendRandomString("dummy_api");
// Updated for cypress 12 because connector builder uses local storage
// docs.cypress.io/guides/references/migration-guide#Simulating-Pre-Test-Isolation-Behavior
cy.clearLocalStorage();
Expand All @@ -49,30 +49,19 @@ describe("Connector builder", { testIsolation: false }, () => {
configureStream();
});

afterEach(() => {
cleanUp();
});

/*
This test assumes it runs before "Read - Without pagination or parameterized requests" since auth will be configured at that
point
*/
it("Fail on invalid auth", () => {
cy.on("uncaught:exception", () => false);
goToView("global");
selectAuthMethod("No Auth");
testStream();
assertTestReadAuthFailure();
});

it("Read - Without pagination or parameterized requests", () => {
configureAuth();
testStream();
assertTestReadItems();
});

/*
All the tests below assume they run after "Read - Without pagination or parameterized requests" in order to have auth
configured
*/
it("Read - Infer schema", () => {
testStream();
assertSchema();
Expand Down Expand Up @@ -129,7 +118,6 @@ describe("Connector builder", { testIsolation: false }, () => {
assertMaxNumberOfSlicesAndPages();
});

// Note: This test cannot be run in isolation! It is dependent on the previous test
it("Sync published version", () => {
publishProject();

Expand Down Expand Up @@ -162,8 +150,9 @@ describe("Connector builder", { testIsolation: false }, () => {
editProjectBuilder(connectorName);
});

// This test assumes the test before is configuring path items/
it("Validate going back to a previously created connector", () => {
configureParameterizedRequests(10);
publishProject();
goToConnectorBuilderProjectsPage();
editProjectBuilder(connectorName);
goToView("0");
Expand Down
49 changes: 17 additions & 32 deletions airbyte-webapp/cypress/pages/connectorBuilderPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,25 @@ export const editProjectBuilder = (name: string) => {
};

export const startFromScratch = () => {
cy.get(startFromScratchButton).click({ force: true });
cy.get(startFromScratchButton).click();
};

export const enterName = (name: string) => {
cy.get(nameInput).clear();
cy.get(nameInput).type(name, { force: true });
cy.get(nameInput).type(name);
};

export const enterUrlBase = (urlBase: string) => {
cy.get(urlBaseInput).type(urlBase, { force: true });
cy.get(urlBaseInput).type(urlBase);
};

export const enterRecordSelector = (recordSelector: string) => {
cy.get(recordSelectorInput).first().type(recordSelector, { force: true });
cy.get(recordSelectorInput).first().type("{enter}", { force: true });
cy.get(recordSelectorInput).first().type(recordSelector);
cy.get(recordSelectorInput).first().type("{enter}");
};

const selectFromDropdown = (selector: string, value: string) => {
cy.get(`${selector} .react-select__dropdown-indicator`).last().click({ force: true });
export const selectFromDropdown = (selector: string, value: string) => {
cy.get(`${selector} .react-select__dropdown-indicator`).last().click();

cy.get(`.react-select__option`).contains(value).click();
};
Expand All @@ -75,51 +75,42 @@ export const openTestInputs = () => {
};

export const enterTestInputs = ({ apiKey }: { apiKey: string }) => {
cy.get(apiKeyInput).type(apiKey, { force: true });
cy.get(apiKeyInput).type(apiKey);
};

export const goToTestPage = (page: number) => {
cy.get(testPageItem).contains(page).click();
};

export const enablePagination = () => {
// force: true is needed because the input has display: none, as we don't want to show default checkboxes
cy.get(togglePaginationInput).check({ force: true });
};

export const disablePagination = () => {
cy.get(togglePaginationInput).uncheck({ force: true });
};

export const configureLimitOffsetPagination = (
limit: string,
limitInto: string,
limitFieldName: string,
offsetInto: string,
offsetFieldName: string
) => {
cy.get(limitInput).type(limit, { force: true });
cy.get(limitInput).type(limit);
selectFromDropdown(injectLimitInto, limitInto);
cy.get(injectLimitFieldName).type(limitFieldName);
selectFromDropdown(injectOffsetInto, offsetInto);
cy.get(injectOffsetFieldName).type(offsetFieldName, { force: true });
cy.get(injectOffsetFieldName).type(offsetFieldName);
};

export const enableParameterizedRequests = () => {
// force: true is needed because the input has display: none, as we don't want to show default checkboxes
cy.get(toggleParameterizedRequestsInput).check({ force: true });
};

export const disableStreamSlicer = () => {
cy.get(toggleParameterizedRequestsInput).uncheck({ force: true });
};

export const configureParameters = (values: string, cursor_field: string) => {
cy.get('[data-testid="tag-input-formValues.streams.0.parameterizedRequests.0.values.value"] input[type="text"]').type(
values,
{
force: true,
}
values
);
cy.get("[name='formValues.streams.0.parameterizedRequests.0.cursor_field']").type(cursor_field, { force: true });
cy.get("[name='formValues.streams.0.parameterizedRequests.0.cursor_field']").type(cursor_field);
};

export const getSlicesFromDropdown = () => {
Expand All @@ -143,22 +134,16 @@ export const getDetectedSchemaElement = () => {
return cy.get('pre[class*="SchemaDiffView"]');
};

export const disableAutoImportSchema = () => {
openStreamSchemaTab();
cy.get("label").contains("Automatically import detected schema").click();
openStreamConfigurationTab();
};

export const addStream = () => {
cy.get(addStreamButton).click();
};

export const enterStreamName = (streamName: string) => {
cy.get(streamNameInput).type(streamName, { force: true });
cy.get(streamNameInput).type(streamName);
};

export const enterUrlPathFromForm = (urlPath: string) => {
cy.get(streamUrlPathFromModal).type(urlPath, { force: true });
cy.get(streamUrlPathFromModal).type(urlPath);
};

export const getUrlPathInput = () => {
Expand All @@ -168,7 +153,7 @@ export const getUrlPathInput = () => {
export const enterUrlPath = (urlPath: string) => {
cy.get('[name="formValues.streams.0.urlPath"]').focus();
cy.get('[name="formValues.streams.0.urlPath"]').clear();
cy.get('[name="formValues.streams.0.urlPath"]').type(urlPath, { force: true });
cy.get('[name="formValues.streams.0.urlPath"]').type(urlPath);
};

export const submitForm = () => {
Expand Down

0 comments on commit ab28dab

Please sign in to comment.