console: e2e test for response transform

This PR adds e2e tests for actions response transform

### Related Issues ✍

https://hasurahq.atlassian.net/browse/GS-247

### Steps to test and verify ✍

1. run console locally from this branch
2. run cypress test (actions -> actionWithTransform)

### Limitations, known bugs & workarounds ✍
Didn't add any negative test cases because there is no validation error from server side for response transform.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6781
GitOrigin-RevId: da1986dba1cb12073b9d6052341906d3350997e9
This commit is contained in:
Varun Choudhary 2022-11-10 19:14:58 +05:30 committed by hasura-bot
parent 1cb217725a
commit bc594e0554
5 changed files with 165 additions and 5 deletions

View File

@ -0,0 +1,107 @@
/**
* Wait for a bunch of requests to be settled before proceeding with the test.
*
* Alternatively, https://github.com/bahmutov/cypress-network-idle could be used
*
* This is a workaround for "element is 'detached' from the DOM" Cypress' error (see the issue
* linked below). Since the UI gets re-rendered because of the requests, this utility ensures that
* all the requests parallelly made by the UI are settled before proceeding with the test. Hance, it
* ensure the UI won't re-render during the next interaction.
*
* What are the requests that must be awaited? By looking at the Cypress Test Runner, they are the
* following, made parallelly or in a rapid series.
* 1. export_metadata
* 2. export_metadata
* 3. export_metadata
* 4. test_webhook_transform
* 5. test_webhook_transform
* 6. test_webhook_transform
* 7. test_webhook_transform
* At the moment of writing, I'm not sure the number of requests are fixed or not. If they are fixed,
* using the cy.intercept `times` options would result in a more expressive and less convoluted code.
*
* To give you an overall idea, this is a timeline of the requests
*
* all requests start all requests end
* | | | |
* |--🚦🔴--1--2--3--4--5--6--7----------------------------1--2--3--4--5--6-7--🚦🟢--|
*
*
* ATTENTION: Despite the defensive approach and the flakiness-removal purpose, this function could
* introduced even more flakiness because of its empiric approach. In case of failures, it must be
* carefully evaluated when/if keeping it or thinking about a better approach.
* In generale, this solution does not scale, at should not be spread among the tests.
*
* @see https://github.com/cypress-io/cypress/issues/7306
* @see https://glebbahmutov.com/blog/detached/
* @see https://github.com/bahmutov/cypress-network-idle
*/
import 'cypress-wait-until';
export function waitForPostCreationRequests() {
let waitCompleted = false;
cy.log('*--- All requests must be settled*');
const pendingRequests = new Map();
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (waitCompleted) return;
Cypress.log({ message: '*--- Request pending*' });
pendingRequests.set(req, true);
req.continue(() => {
Cypress.log({ message: '*--- Request settled*' });
pendingRequests.delete(req);
});
});
Cypress.log({ message: '*--- Waiting for the first request to start*' });
// Check if at least one request has been caught. This check must protect from the following case
//
// check requests start test failure, the requests got the UI re-rendered
// | | |
// |--🚦🔴----⚠️---🚦🟢-------1-2-3-4-5-6-7-1----------💥
//
// where checking that "there are no pending requests" falls in the false positive case where
// there are no pending requests because no one started at all.
//
// The check runs every millisecond to be 100% sure that no request can escape (ex. because of a
// super fast server). A false-negative case represented here
//
// requests start requests end check check test failure, no first request caught
// | | | | | | |
// |--🚦🔴--1-2-3-4-5-6-7-1-2-3-4-5-6-7--⚠️------------------⚠️------------------💥
cy.waitUntil(() => pendingRequests.size > 0, {
timeout: 5000, // 5 seconds is the default Cypress wait for a request to start
interval: 1,
errorMsg: 'No first request caught',
});
Cypress.log({ message: '*--- Waiting for all the requests to start*' });
// Let pass some time to collect all the requests. Otherwise, it could detect that the first
// request complete and go on with the test, even if another one will be performed in a while.
//
// This fixed wait protects from the following timeline
//
// 1st request start first request end other requests start test failure, the requests got the UI re-rendered
// | | | |
// |--🚦🔴---1---------------------1----🚦🟢----------------2-3-4-5-6-7-1----------💥
//
// Obviously, it is an empiric waiting, that also slows down the test.
cy.wait(500);
Cypress.log({ message: '*--- Waiting for all the requests to be settled*' });
cy.waitUntil(() => pendingRequests.size === 0, {
timeout: 30000, // 30 seconds is the default Cypress wait for the request to complete
errorMsg: 'Some requests are not settled yet',
}).then(() => {
waitCompleted = true;
});
}

View File

@ -1,4 +1,5 @@
import { testMode } from '../../../helpers/common';
import { waitForPostCreationRequests } from '../query/utils/requests/waitForPostCreationRequests';
import { logMetadataRequests } from './utils/requests/logMetadataRequests';
import { loginActionMustNotExist } from './utils/testState/loginActionMustNotExist';
@ -173,6 +174,8 @@ if (testMode !== 'cli') {
cy.log('**--- Type in the Payload Transform Request Body textarea**');
cy.get('@payloadTransformRequestBody')
.wait(500)
.clearConsoleTextarea()
.clearConsoleTextarea()
.type(
`{
@ -189,8 +192,44 @@ if (testMode !== 'cli') {
);
});
cy.log('**------------------------------**');
cy.log('**--- Step 1.5: Add Response Transform**');
cy.log('**------------------------------**');
// --------------------
cy.log('**--- Click the Add Response Transform button**');
cy.contains('Add Response Transform').click();
// --------------------
cy.get('[data-cy="Change Response"]').within(() => {
// Assign an alias to the most unclear selectors for future references
cy.get('textarea').eq(0).as('responseTransformResponseBody');
cy.log('**--- Type in the Response Transform Response Body textarea**');
cy.get('@responseTransformResponseBody')
.wait(500)
.clearConsoleTextarea()
.clearConsoleTextarea()
.type(
`{
"userInfo": {
"name": {{$body.input.username}},
"password": {{$body.input.password}},
"type": {{$body.action.name}}
`,
// delay is set to 1 because setting it to 0 causes the test to fail because writes
// something like
// "name": {{$body.input.username}}name
// in the textarea (the closing "name" is a mistake)
{ force: true, delay: 1, parseSpecialCharSequences: false }
);
});
// --------------------
cy.log('**--- Click the Create button**');
// cy.wait(1000) because of debounce
cy.wait(1000);
cy.getBySel('create-action-btn').click();
// --------------------
@ -212,8 +251,8 @@ if (testMode !== 'cli') {
// cy.log('**------------------------------**');
// // --------------------
// cy.log('**--- Wait all the requests to be settled**');
// waitForPostCreationRequests();
cy.log('**--- Wait all the requests to be settled**');
waitForPostCreationRequests();
// cy.get('[data-cy="Change Request Options"]').within(() => {
// // --------------------
@ -264,7 +303,7 @@ if (testMode !== 'cli') {
// --------------------
cy.log('**--- Set the prompt value**');
cy.window().then(win => cy.stub(win, 'prompt').returns('login'));
cy.log('**--- Click the Delete button**');
cy.getBySel('delete-action').click();

View File

@ -201,13 +201,14 @@
"cross-env": "7.0.2",
"css-loader": "3.5.3",
"cypress": "^10.4.0",
"cypress-wait-until": "^1.7.2",
"dedent": "0.7.0",
"dotenv": "5.0.1",
"eslint": "6.8.0",
"eslint-config-airbnb": "16.1.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-chai-friendly": "0.4.1",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-cypress": "2.12.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest-dom": "^3.9.0",
"eslint-plugin-jsx-a11y": "6.4.1",
@ -18746,6 +18747,12 @@
"node": ">=12.0.0"
}
},
"node_modules/cypress-wait-until": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz",
"integrity": "sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q==",
"dev": true
},
"node_modules/cypress/node_modules/@types/node": {
"version": "14.18.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.21.tgz",
@ -57797,6 +57804,12 @@
}
}
},
"cypress-wait-until": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.2.tgz",
"integrity": "sha512-uZ+M8/MqRcpf+FII/UZrU7g1qYZ4aVlHcgyVopnladyoBrpoaMJ4PKZDrdOJ05H5RHbr7s9Tid635X3E+ZLU/Q==",
"dev": true
},
"damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",

View File

@ -270,6 +270,7 @@
"cross-env": "7.0.2",
"css-loader": "3.5.3",
"cypress": "^10.4.0",
"cypress-wait-until": "^1.7.2",
"dedent": "0.7.0",
"dotenv": "5.0.1",
"eslint": "6.8.0",

View File

@ -50,7 +50,7 @@ const ResponseTransforms: React.FC<PayloadOptionsTransformsProps> = ({
return (
<div
className="m-md pl-lg pr-sm border-l border-l-gray-400"
data-cy="Change Payload"
data-cy="Change Response"
>
<div className="mb-md">
<NumberedSidebar