mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-10-05 14:28:08 +03:00
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:
parent
1cb217725a
commit
bc594e0554
@ -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;
|
||||
});
|
||||
}
|
@ -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();
|
||||
|
||||
|
15
console/package-lock.json
generated
15
console/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user