graphql-engine/console/cypress/support/contractIntercept/contractIntercept.ts
Stefano Magni 5764174087 test(console): Skip a lot of flaky E2E tests
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5535
GitOrigin-RevId: 0544edf4d8fdd1a6a8cc326a8b107ee47d2389c3
2022-08-31 08:02:51 +00:00

169 lines
5.1 KiB
TypeScript

import type {
ContractRequest,
RunningTestsState,
StartContractInterceptOptions,
} from './types';
import { checkAndGetTestInfo } from './helpers/checkAndGetTestInfo';
import { generateEmptyTestState } from './helpers/generateEmptyTestState';
const runningTestState: RunningTestsState = {};
/**
* A wrapper around `cy.intercept` that allows intercepting and recording the request/response series
* that made up the contract.
*
* It could be useful for sharing the contract's fixtures with the server folks.
*
* Be aware of the current limitations:
* 1. Only one test at the time is supported. `runningTestState` can store more tests at once but
* the fixture files are all saved starting from "1 - ", even if related to different tests
* 2. If you do not call cy.haltContractIntercept, no fixture files will be saved
*
* @see https://github.com/hasura/graphql-engine-mono/issues/4601
*/
function startContractIntercept(
startContractInterceptOptions: StartContractInterceptOptions,
url: string
) {
const { thisTest, mode, createFixtureName } = startContractInterceptOptions;
const { testTitle, testPath } = checkAndGetTestInfo(thisTest);
if (mode === 'disabled') {
Cypress.log({
message: `*🤝 ❌ No Contract will be recorded for ${testTitle}*`,
});
return;
}
Cypress.log({
message: `*🤝 ✅ Contract will be recorded for ${testTitle}*`,
});
runningTestState[testTitle] ??= generateEmptyTestState(testPath, testTitle);
if (Object.keys(runningTestState).length > 1) {
throw new Error(`startContractIntercept support only one test at a time`);
}
// Start intercepting the requests
cy.intercept(url, request => {
// The recorded could have been halted
if (runningTestState[testTitle].halted) {
Cypress.log({
message: `*🤝 ❌ Contract recording has been halted for: ${testTitle}*`,
});
return;
}
const fixtureName = createFixtureName(request);
if (fixtureName.includes('\\') || fixtureName.includes('/')) {
throw new Error(
`createFixtureName cannot return names that includes / or \\ like ${fixtureName}`
);
}
const contractLength = runningTestState[testTitle].contract.length;
// start from 1
const fixtureIndex = contractLength + 1;
const fixtureFileName = `${fixtureIndex}-${fixtureName}.json`;
const recorded: ContractRequest = {
readme:
'////////// This fixture has been automatically generated through cy.startContractIntercept //////////',
request,
fixtureName,
fixtureFileName,
// Temporary, empty, response
response: {
statusCode: undefined,
headers: undefined,
body: undefined,
},
};
// Add the request to the Contract
runningTestState[testTitle].contract.push(recorded);
Cypress.log({
message: `*🤝 ✅ Recorded ${fixtureFileName} in the contract*`,
consoleProps: () => request,
});
request.continue(response => {
// Add the request to the Contract too
recorded.response = response;
});
});
}
/**
* Halt recording the contract and save the fixture files.
* Please note that it must be called just once
*/
function haltContractIntercept(options: {
thisTest: Mocha.Context;
saveFixtureFiles?: boolean;
}) {
const { thisTest, saveFixtureFiles = true } = options;
const { testTitle, testPath } = checkAndGetTestInfo(thisTest);
if (!saveFixtureFiles) {
Cypress.log({
message: `*🤝 ❌ No fixtures will be saved for this test: ${testTitle}*`,
});
return;
}
if (runningTestState[testTitle].halted) {
Cypress.log({
message: `*🤝 ❌ Contract recording for this test has already been halted: ${testTitle}*`,
});
}
// Halt recording the requests for the current test.
// Please note that must be done asynchronously because of the double-run nature of the Cypress tests.
cy.wrap(null).then(() => {
Cypress.log({
message: `*🤝 ❌ Halting the contract recording for this test: ${testTitle}*`,
});
runningTestState[testTitle].halted = true;
});
// Split the current path
cy.task('splitPath', { path: testPath }).then(result => {
const splittedPath = result as string[];
// Remove the file name
splittedPath.pop();
// Create the directory
cy.task('joinPath', { path: [...splittedPath, 'fixtures'] }).then(path => {
cy.task('mkdirSync', {
dir: path as string,
});
});
const testState = runningTestState[testTitle];
// Save all the files
for (let i = 0, n = testState.contract.length; i < n; i++) {
const request = testState.contract[i];
cy.task('joinPath', {
// Stores the fixture files close to the test file, in a "fixtures" directory
path: [...splittedPath, 'fixtures', request.fixtureFileName],
}).then(filePath => {
// Save the fixture file
cy.task('writeFileSync', {
file: filePath as string,
data: JSON.stringify(request, null, 2),
});
});
}
});
}
Cypress.Commands.add('startContractIntercept', startContractIntercept);
Cypress.Commands.add('haltContractIntercept', haltContractIntercept);