@jahia/cypress 7.4.0 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +69 -2
- package/dist/index.js +6 -2
- package/dist/injections/bash-data.d.ts +1 -0
- package/dist/injections/bash-data.js +57 -0
- package/dist/injections/chars-data.d.ts +1 -0
- package/dist/injections/chars-data.js +25 -0
- package/dist/injections/htmlentities-data.d.ts +1 -0
- package/dist/injections/htmlentities-data.js +22 -0
- package/dist/injections/numbers-data.d.ts +1 -0
- package/dist/injections/numbers-data.js +66 -0
- package/dist/injections/sql-data.d.ts +1 -0
- package/dist/injections/sql-data.js +82 -0
- package/dist/injections/xss-data.d.ts +1 -0
- package/dist/injections/xss-data.js +740 -0
- package/dist/page-object/baseComponent.d.ts +1 -2
- package/dist/page-object/baseComponent.js +10 -6
- package/dist/page-object/basePage.js +1 -1
- package/dist/page-object/html/iframe.d.ts +0 -1
- package/dist/page-object/html/iframe.js +2 -2
- package/dist/page-object/html/index.js +6 -2
- package/dist/page-object/index.js +6 -2
- package/dist/page-object/material/index.js +6 -2
- package/dist/page-object/material/muiinput.d.ts +0 -1
- package/dist/page-object/material/muiinput.js +1 -1
- package/dist/page-object/material/muiradio.js +1 -1
- package/dist/page-object/moonstone/accordion.d.ts +0 -1
- package/dist/page-object/moonstone/accordion.js +2 -2
- package/dist/page-object/moonstone/button.js +1 -1
- package/dist/page-object/moonstone/collapsible.js +1 -1
- package/dist/page-object/moonstone/dropdown.js +2 -2
- package/dist/page-object/moonstone/index.js +6 -2
- package/dist/page-object/moonstone/menu.js +9 -9
- package/dist/page-object/moonstone/pagination.js +3 -3
- package/dist/page-object/moonstone/primaryNav.js +2 -2
- package/dist/page-object/moonstone/secondaryNav.js +1 -1
- package/dist/page-object/moonstone/table.d.ts +0 -1
- package/dist/page-object/moonstone/table.js +5 -5
- package/dist/page-object/utils.d.ts +0 -1
- package/dist/page-object/utils.js +11 -12
- package/dist/plugins/env.js +2 -2
- package/dist/plugins/index.js +6 -2
- package/dist/plugins/registerPlugins.js +2 -2
- package/dist/support/apollo/apollo.d.ts +5 -4
- package/dist/support/apollo/apollo.js +80 -18
- package/dist/support/apollo/apolloClient.d.ts +1 -2
- package/dist/support/apollo/apolloClient.js +7 -7
- package/dist/support/apollo/index.js +6 -2
- package/dist/support/apollo/links.d.ts +1 -1
- package/dist/support/apollo/links.js +5 -6
- package/dist/support/browserHelper.d.ts +10 -0
- package/dist/support/browserHelper.js +167 -0
- package/dist/support/commands.js +1 -1
- package/dist/support/fixture.d.ts +1 -1
- package/dist/support/fixture.js +11 -7
- package/dist/support/index.d.ts +3 -0
- package/dist/support/index.js +9 -2
- package/dist/support/jfaker.d.ts +60 -0
- package/dist/support/jfaker.js +241 -0
- package/dist/support/jsErrorsLogger.js +13 -9
- package/dist/support/login.d.ts +0 -1
- package/dist/support/login.js +2 -2
- package/dist/support/logout.d.ts +0 -1
- package/dist/support/logout.js +1 -1
- package/dist/support/modSince.d.ts +52 -0
- package/dist/support/modSince.js +180 -0
- package/dist/support/provisioning/executeGroovy.d.ts +1 -1
- package/dist/support/provisioning/executeGroovy.js +42 -3
- package/dist/support/provisioning/index.js +6 -2
- package/dist/support/provisioning/installConfig.d.ts +0 -1
- package/dist/support/provisioning/installConfig.js +3 -3
- package/dist/support/provisioning/installModule.d.ts +0 -1
- package/dist/support/provisioning/installModule.js +1 -1
- package/dist/support/provisioning/runProvisioningScript.d.ts +4 -5
- package/dist/support/provisioning/runProvisioningScript.js +86 -9
- package/dist/support/provisioning/uninstallModule.d.ts +0 -1
- package/dist/support/provisioning/uninstallModule.js +1 -1
- package/dist/support/registerSupport.js +35 -1
- package/dist/support/repeatUntil.d.ts +1 -2
- package/dist/support/repeatUntil.js +2 -2
- package/dist/support/testStep.js +2 -2
- package/dist/utils/ClusterHelper.js +1 -1
- package/dist/utils/ExportHelper.d.ts +2 -2
- package/dist/utils/ExportHelper.js +14 -10
- package/dist/utils/GraphQLHelper.js +21 -17
- package/dist/utils/JCRHelper.d.ts +1 -1
- package/dist/utils/JCRHelper.js +1 -1
- package/dist/utils/JahiaPlatformHelper.js +2 -2
- package/dist/utils/Logger.js +6 -6
- package/dist/utils/PublicationAndWorkflowHelper.js +3 -3
- package/dist/utils/SAMHelper.d.ts +1 -1
- package/dist/utils/SAMHelper.js +4 -4
- package/dist/utils/SiteHelper.js +2 -2
- package/dist/utils/UsersHelper.js +2 -2
- package/dist/utils/VanityUrlHelper.js +1 -1
- package/dist/utils/index.js +6 -2
- package/docs/browser-helper.md +158 -0
- package/docs/jfaker.md +450 -0
- package/package.json +13 -10
- package/src/injections/bash-data.ts +54 -0
- package/src/injections/chars-data.ts +22 -0
- package/src/injections/htmlentities-data.ts +19 -0
- package/src/injections/numbers-data.ts +63 -0
- package/src/injections/sql-data.ts +79 -0
- package/src/injections/xss-data.ts +737 -0
- package/src/page-object/baseComponent.ts +6 -6
- package/src/page-object/html/iframe.ts +3 -3
- package/src/page-object/material/muiinput.ts +1 -1
- package/src/page-object/material/muiradio.ts +1 -1
- package/src/page-object/moonstone/accordion.ts +1 -1
- package/src/page-object/moonstone/button.ts +1 -1
- package/src/page-object/moonstone/collapsible.ts +1 -1
- package/src/page-object/moonstone/dropdown.ts +1 -1
- package/src/page-object/moonstone/menu.ts +1 -1
- package/src/page-object/moonstone/pagination.ts +1 -1
- package/src/page-object/moonstone/primaryNav.ts +1 -1
- package/src/page-object/moonstone/secondaryNav.ts +1 -1
- package/src/page-object/moonstone/table.ts +2 -2
- package/src/support/apollo/apollo.ts +74 -11
- package/src/support/apollo/links.ts +1 -2
- package/src/support/browserHelper.ts +186 -0
- package/src/support/index.ts +3 -0
- package/src/support/jfaker.ts +245 -0
- package/src/support/modSince.ts +222 -0
- package/src/support/provisioning/executeGroovy.md +7 -1
- package/src/support/provisioning/executeGroovy.ts +46 -2
- package/src/support/provisioning/runProvisioningScript.ts +89 -12
- package/src/support/registerSupport.ts +29 -0
- package/tests/cypress/e2e/jfaker.spec.ts +411 -0
- package/tests/cypress/e2e/modSince.spec.ts +306 -0
- package/tests/cypress.config.ts +23 -0
- package/tests/package.json +41 -0
- package/tests/reporter-config.json +13 -0
- package/tests/yarn.lock +8578 -0
- package/tsconfig.json +3 -0
|
@@ -4,12 +4,12 @@ import Chainer = Cypress.Chainer;
|
|
|
4
4
|
export type ComponentType<Component> = { new(p: Chainable<JQuery>, assertion?: (s: JQuery) => void): Component, defaultSelector: string };
|
|
5
5
|
|
|
6
6
|
export class BaseComponent {
|
|
7
|
-
static defaultSelector = ''
|
|
8
|
-
static count = 0
|
|
7
|
+
static defaultSelector = '';
|
|
8
|
+
static count = 0;
|
|
9
9
|
|
|
10
|
-
element: Chainable<JQuery
|
|
11
|
-
id: number
|
|
12
|
-
assertion?: (s: JQuery) => void
|
|
10
|
+
element: Chainable<JQuery>;
|
|
11
|
+
id: number;
|
|
12
|
+
assertion?: (s: JQuery) => void;
|
|
13
13
|
|
|
14
14
|
constructor(element: Chainable<JQuery>, assertion?: (s: JQuery) => void) {
|
|
15
15
|
this.id = BaseComponent.count++;
|
|
@@ -27,5 +27,5 @@ export class BaseComponent {
|
|
|
27
27
|
|
|
28
28
|
should: Chainer<JQuery> = (arg, ...others) => {
|
|
29
29
|
return cy.get('@component' + this.id, {log: false}).should(arg, ...others);
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
}
|
|
@@ -2,9 +2,9 @@ import {BaseComponent} from '../baseComponent';
|
|
|
2
2
|
import Chainable = Cypress.Chainable;
|
|
3
3
|
|
|
4
4
|
export class IFrame extends BaseComponent {
|
|
5
|
-
static defaultSelector = 'iframe'
|
|
5
|
+
static defaultSelector = 'iframe';
|
|
6
6
|
|
|
7
|
-
private body: JQuery<HTMLElement
|
|
7
|
+
private body: JQuery<HTMLElement>;
|
|
8
8
|
|
|
9
9
|
constructor(element: Chainable<JQuery>, assertion?: (s: JQuery) => void) {
|
|
10
10
|
super(element, assertion);
|
|
@@ -13,7 +13,7 @@ export class IFrame extends BaseComponent {
|
|
|
13
13
|
const fr: HTMLFrameElement = f[0] as HTMLFrameElement;
|
|
14
14
|
expect(fr.contentWindow.location.href).not.equals('about:blank');
|
|
15
15
|
expect(fr.contentWindow.document.readyState).equals('complete');
|
|
16
|
-
// eslint-disable-next-line
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
17
17
|
expect(fr.contentDocument.body).not.be.empty;
|
|
18
18
|
})
|
|
19
19
|
.its('0.contentDocument.body').as('framebody' + this.id);
|
|
@@ -2,7 +2,7 @@ import {BaseComponent} from '../baseComponent';
|
|
|
2
2
|
import Chainable = Cypress.Chainable;
|
|
3
3
|
|
|
4
4
|
export class Accordion extends BaseComponent {
|
|
5
|
-
static defaultSelector = '.moonstone-accordion'
|
|
5
|
+
static defaultSelector = '.moonstone-accordion';
|
|
6
6
|
|
|
7
7
|
click(itemName: string): Accordion {
|
|
8
8
|
this.get().find(`section.moonstone-accordionItem header[aria-controls="${itemName}"]`).click();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {BaseComponent} from '../baseComponent';
|
|
2
2
|
|
|
3
3
|
export class Collapsible extends BaseComponent {
|
|
4
|
-
static defaultSelector = '.moonstone-collapsible'
|
|
4
|
+
static defaultSelector = '.moonstone-collapsible';
|
|
5
5
|
|
|
6
6
|
collapse(): Collapsible {
|
|
7
7
|
this.get().children('div').then($child => {
|
|
@@ -3,7 +3,7 @@ import {Menu} from './menu';
|
|
|
3
3
|
import {getComponent} from '../utils';
|
|
4
4
|
|
|
5
5
|
export class Dropdown extends BaseComponent {
|
|
6
|
-
static defaultSelector = '.moonstone-dropdown_container'
|
|
6
|
+
static defaultSelector = '.moonstone-dropdown_container';
|
|
7
7
|
|
|
8
8
|
select(item: string): Dropdown {
|
|
9
9
|
this.get().click();
|
|
@@ -3,7 +3,7 @@ import {getComponentByRole} from '../utils';
|
|
|
3
3
|
import Chainable = Cypress.Chainable;
|
|
4
4
|
|
|
5
5
|
export class Menu extends BaseComponent {
|
|
6
|
-
static defaultSelector = '.moonstone-menu:not(.moonstone-hidden)'
|
|
6
|
+
static defaultSelector = '.moonstone-menu:not(.moonstone-hidden)';
|
|
7
7
|
static overlaySelector = '.moonstone-menu_overlay';
|
|
8
8
|
|
|
9
9
|
submenu(item: string, menu: string): Menu {
|
|
@@ -4,7 +4,7 @@ import Chainable = Cypress.Chainable;
|
|
|
4
4
|
import {getComponentByRole} from '../utils';
|
|
5
5
|
|
|
6
6
|
export class Pagination extends BaseComponent {
|
|
7
|
-
static defaultSelector = '.moonstone-tablePagination'
|
|
7
|
+
static defaultSelector = '.moonstone-tablePagination';
|
|
8
8
|
|
|
9
9
|
clickNextPage(): Pagination {
|
|
10
10
|
getComponentByRole(Button, 'table-pagination-button-next-page', this).click();
|
|
@@ -2,7 +2,7 @@ import {BaseComponent} from '../baseComponent';
|
|
|
2
2
|
import Chainable = Cypress.Chainable;
|
|
3
3
|
|
|
4
4
|
export class PrimaryNav extends BaseComponent {
|
|
5
|
-
static defaultSelector = '.moonstone-primaryNav'
|
|
5
|
+
static defaultSelector = '.moonstone-primaryNav';
|
|
6
6
|
|
|
7
7
|
click(itemName: string): void {
|
|
8
8
|
this.get().find(`.moonstone-primaryNavItem[role="${itemName}"]`).click();
|
|
@@ -3,7 +3,7 @@ import {Menu} from './menu';
|
|
|
3
3
|
import {getComponent, getComponentByContent, getComponentByIndex, getComponentBySelector} from '../utils';
|
|
4
4
|
|
|
5
5
|
export class Table extends BaseComponent {
|
|
6
|
-
static defaultSelector = '.moonstone-Table'
|
|
6
|
+
static defaultSelector = '.moonstone-Table';
|
|
7
7
|
|
|
8
8
|
getRows(assertion?: (s: JQuery) => void): TableRow {
|
|
9
9
|
return getComponent(TableRow, this, assertion);
|
|
@@ -19,7 +19,7 @@ export class Table extends BaseComponent {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export class TableRow extends BaseComponent {
|
|
22
|
-
static defaultSelector = '.moonstone-TableBody .moonstone-TableRow'
|
|
22
|
+
static defaultSelector = '.moonstone-TableBody .moonstone-TableRow';
|
|
23
23
|
|
|
24
24
|
contextMenu(): Menu {
|
|
25
25
|
this.get().rightclick();
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
/// <reference types="cypress" />
|
|
5
5
|
|
|
6
6
|
import {ApolloClient, ApolloQueryResult, FetchResult, MutationOptions, QueryOptions} from '@apollo/client/core';
|
|
7
|
+
import {DocumentNode} from '@apollo/client/core';
|
|
7
8
|
import gql from 'graphql-tag';
|
|
9
|
+
import {FieldNode, getOperationAST, print} from 'graphql';
|
|
8
10
|
|
|
9
11
|
declare global {
|
|
10
12
|
namespace Cypress {
|
|
@@ -15,8 +17,8 @@ declare global {
|
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
export type FileQueryOptions = Partial<QueryOptions> & { queryFile?: string }
|
|
19
|
-
export type FileMutationOptions = Partial<MutationOptions> & { mutationFile?: string }
|
|
20
|
+
export type FileQueryOptions = Partial<QueryOptions> & { queryFile?: string; sourcePackage?: string }
|
|
21
|
+
export type FileMutationOptions = Partial<MutationOptions> & { mutationFile?: string; sourcePackage?: string }
|
|
20
22
|
export type ApolloOptions = (QueryOptions | MutationOptions | FileQueryOptions | FileMutationOptions) & Partial<Cypress.Loggable>;
|
|
21
23
|
|
|
22
24
|
function isQuery(options: ApolloOptions): options is QueryOptions {
|
|
@@ -31,51 +33,112 @@ function isMutationFile(options: ApolloOptions): options is FileMutationOptions
|
|
|
31
33
|
return (<FileMutationOptions>options).mutationFile !== undefined;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
function getOperationLabel(doc: DocumentNode, opType: 'Query' | 'Mutation'): string {
|
|
37
|
+
const opDef = getOperationAST(doc);
|
|
38
|
+
if (opDef?.name?.value) {
|
|
39
|
+
return `[${opType}] ${opDef.name.value}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Anonymous operation: traverse up to 2 selection levels for a meaningful label
|
|
43
|
+
const firstSel = opDef?.selectionSet?.selections?.[0];
|
|
44
|
+
if (firstSel?.kind === 'Field') {
|
|
45
|
+
const firstName = (firstSel as FieldNode).name.value;
|
|
46
|
+
const secondSel = (firstSel as FieldNode).selectionSet?.selections?.[0];
|
|
47
|
+
if (secondSel?.kind === 'Field') {
|
|
48
|
+
return `[${opType}] ${firstName} › ${(secondSel as FieldNode).name.value}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return `[${opType}] ${firstName}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `[${opType}]`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getQueryBody(doc: DocumentNode): string {
|
|
58
|
+
return doc?.loc?.source?.body ?? print(doc);
|
|
59
|
+
}
|
|
60
|
+
|
|
34
61
|
// eslint-disable-next-line default-param-last, @typescript-eslint/no-shadow
|
|
35
62
|
export const apollo = function (apollo: ApolloClient<any> = this.currentApolloClient, options: ApolloOptions): void {
|
|
36
63
|
let result : ApolloQueryResult<any> | FetchResult;
|
|
37
64
|
let logger : Cypress.Log;
|
|
65
|
+
let duration: number;
|
|
38
66
|
const optionsWithDefaultCache: ApolloOptions = {fetchPolicy: 'no-cache', ...options};
|
|
39
67
|
|
|
40
68
|
if (!apollo) {
|
|
41
69
|
cy.apolloClient().apollo(optionsWithDefaultCache);
|
|
42
70
|
} else if (isQueryFile(optionsWithDefaultCache)) {
|
|
43
|
-
const {queryFile, ...apolloOptions} = optionsWithDefaultCache
|
|
71
|
+
const {queryFile, sourcePackage, ...apolloOptions} = optionsWithDefaultCache as FileQueryOptions & Partial<Cypress.Loggable>;
|
|
44
72
|
cy.fixture(queryFile).then(content => {
|
|
45
|
-
|
|
73
|
+
const fileLabel = sourcePackage ? `${queryFile} @ ${sourcePackage}` : queryFile;
|
|
74
|
+
cy.apollo({query: gql(content), ...apolloOptions, _sourceFile: fileLabel} as ApolloOptions);
|
|
46
75
|
});
|
|
47
76
|
} else if (isMutationFile(optionsWithDefaultCache)) {
|
|
48
|
-
const {mutationFile, ...apolloOptions} = optionsWithDefaultCache
|
|
77
|
+
const {mutationFile, sourcePackage, ...apolloOptions} = optionsWithDefaultCache as FileMutationOptions & Partial<Cypress.Loggable>;
|
|
49
78
|
cy.fixture(mutationFile).then(content => {
|
|
50
|
-
|
|
79
|
+
const fileLabel = sourcePackage ? `${mutationFile} @ ${sourcePackage}` : mutationFile;
|
|
80
|
+
cy.apollo({mutation: gql(content), ...apolloOptions, _sourceFile: fileLabel} as ApolloOptions);
|
|
51
81
|
});
|
|
52
82
|
} else {
|
|
53
83
|
const {log = true, ...apolloOptions} = optionsWithDefaultCache;
|
|
84
|
+
|
|
85
|
+
const doc = isQuery(apolloOptions) ?
|
|
86
|
+
(apolloOptions as QueryOptions).query :
|
|
87
|
+
(apolloOptions as MutationOptions).mutation;
|
|
88
|
+
const opType = isQuery(apolloOptions) ? 'Query' : 'Mutation';
|
|
89
|
+
const operationLabel = getOperationLabel(doc, opType);
|
|
90
|
+
const queryBody = getQueryBody(doc);
|
|
91
|
+
const variables = (apolloOptions as any).variables;
|
|
92
|
+
const sourceLabel = (optionsWithDefaultCache as any)._sourceFile ? ` (${(optionsWithDefaultCache as any)._sourceFile})` : '';
|
|
93
|
+
const variablesLabel = variables && Object.keys(variables).length > 0 ?
|
|
94
|
+
` — ${JSON.stringify(variables)}` :
|
|
95
|
+
'';
|
|
96
|
+
|
|
54
97
|
if (log) {
|
|
55
98
|
logger = Cypress.log({
|
|
56
99
|
autoEnd: false,
|
|
57
100
|
name: 'apollo',
|
|
58
101
|
displayName: 'apollo',
|
|
59
|
-
message:
|
|
102
|
+
message: `${operationLabel}${sourceLabel}${variablesLabel}`,
|
|
60
103
|
consoleProps: () => {
|
|
104
|
+
const errors = (result as any)?.errors ?? (result as any)?.graphQLErrors ?? null;
|
|
105
|
+
const isCaughtError = result instanceof Error;
|
|
106
|
+
const hasErrors = (errors?.length > 0) || isCaughtError;
|
|
61
107
|
return {
|
|
62
|
-
|
|
108
|
+
Operation: operationLabel,
|
|
109
|
+
Variables: variables ?? {},
|
|
110
|
+
[`${opType} Body`]: queryBody,
|
|
111
|
+
Duration: duration === undefined ? 'pending' : `${duration}ms`,
|
|
112
|
+
Status: hasErrors ?
|
|
113
|
+
`error${isCaughtError ? `: ${(result as unknown as Error).message}` : ''}` :
|
|
114
|
+
'success',
|
|
115
|
+
Data: (result as any)?.data ?? null,
|
|
116
|
+
Errors: errors,
|
|
63
117
|
Yielded: result
|
|
64
118
|
};
|
|
65
119
|
}
|
|
66
120
|
});
|
|
67
121
|
}
|
|
68
122
|
|
|
69
|
-
|
|
123
|
+
const startTime = Date.now();
|
|
124
|
+
cy.wrap({}, {log: false})
|
|
70
125
|
.then(() => (isQuery(optionsWithDefaultCache) ? apollo.query(optionsWithDefaultCache).catch(error => {
|
|
71
|
-
cy.log(`Caught
|
|
126
|
+
cy.log(`Caught GraphQL query error: ${(error as any)?.message ?? JSON.stringify(error)}`);
|
|
72
127
|
return error;
|
|
73
128
|
}) : apollo.mutate(optionsWithDefaultCache).catch(error => {
|
|
74
|
-
cy.log(`Caught
|
|
129
|
+
cy.log(`Caught GraphQL mutation error: ${(error as any)?.message ?? JSON.stringify(error)}`);
|
|
75
130
|
return error;
|
|
76
131
|
}))
|
|
77
132
|
.then(r => {
|
|
78
133
|
result = r;
|
|
134
|
+
duration = Date.now() - startTime;
|
|
135
|
+
if (logger) {
|
|
136
|
+
const errors = (r as any)?.errors ?? (r as any)?.graphQLErrors;
|
|
137
|
+
const hasErrors = (r instanceof Error) || (errors?.length > 0);
|
|
138
|
+
const prefix = hasErrors ? '❌ ' : '✅ ';
|
|
139
|
+
logger.set('message', `${prefix}${operationLabel}${sourceLabel}${variablesLabel}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
79
142
|
logger?.end();
|
|
80
143
|
return r;
|
|
81
144
|
})
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
2
1
|
import {HttpLink} from '@apollo/client/link/http';
|
|
3
2
|
import fetch from 'cross-fetch';
|
|
4
3
|
import {setContext} from '@apollo/client/link/context';
|
|
@@ -7,7 +6,7 @@ interface ApolloRequestInit extends RequestInit {
|
|
|
7
6
|
formData?: FormData
|
|
8
7
|
}
|
|
9
8
|
|
|
10
|
-
export const formDataHttpLink = (baseUrl: string, headers:
|
|
9
|
+
export const formDataHttpLink = (baseUrl: string, headers: Record<string, string>) => {
|
|
11
10
|
return new HttpLink({
|
|
12
11
|
uri: `${baseUrl}/modules/graphql`,
|
|
13
12
|
headers,
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Contains helpers for printing or clearing browser's storage and cookies.
|
|
3
|
+
* These are intended for interactive debugging and log full values by design.
|
|
4
|
+
* Use with caution in automated tests to avoid exposing sensitive data in logs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Prints cookie details in a structured format for debugging.
|
|
9
|
+
*
|
|
10
|
+
* Note: This helper logs the full cookie value by design.
|
|
11
|
+
* @param {Cypress.Cookie} cookie Cookie object returned by Cypress.
|
|
12
|
+
* @returns {void}
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
const printCookieValues = (cookie: Cypress.Cookie): void => {
|
|
16
|
+
const cookieType = cookie.expiry ? 'Persistent' : 'Session';
|
|
17
|
+
const expiryDate = cookie.expiry ? new Date(cookie.expiry * 1000).toISOString() : 'Session only';
|
|
18
|
+
const daysUntilExpiry = cookie.expiry ? Math.round(((cookie.expiry * 1000) - Date.now()) / 1000 / 60 / 60 / 24) : null;
|
|
19
|
+
|
|
20
|
+
cy.log('-'.repeat(60));
|
|
21
|
+
cy.log(`Cookie: ${cookie.name}`);
|
|
22
|
+
cy.log('-'.repeat(60));
|
|
23
|
+
cy.log(`Type: ${cookieType}`);
|
|
24
|
+
cy.log(`Value: ${cookie.value}`);
|
|
25
|
+
cy.log(`Domain: ${cookie.domain}`);
|
|
26
|
+
cy.log(`Path: ${cookie.path}`);
|
|
27
|
+
cy.log(`Secure: ${cookie.secure ? '✔ Yes' : '✘ No'}`);
|
|
28
|
+
cy.log(`HttpOnly: ${cookie.httpOnly ? '✔ Yes' : '✘ No'}`);
|
|
29
|
+
cy.log(`SameSite: ${cookie.sameSite || '(not set)'}`);
|
|
30
|
+
|
|
31
|
+
if (cookie.expiry) {
|
|
32
|
+
cy.log(`Expires: ${expiryDate}`);
|
|
33
|
+
cy.log(`Days left: ${daysUntilExpiry} days`);
|
|
34
|
+
cy.log(`Unix time: ${cookie.expiry}`);
|
|
35
|
+
} else {
|
|
36
|
+
cy.log('Expires: When browser closes (session cookie)');
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Clears cookies based on their persistence type.
|
|
42
|
+
* @param {'session'|'persistent'} [type='session'] Cookie category to clear.
|
|
43
|
+
* @returns {Cypress.Chainable<void>} Cypress chainable resolved when clearing is complete.
|
|
44
|
+
*/
|
|
45
|
+
const clearCookiesByType = (type: 'session' | 'persistent' = 'session'): Cypress.Chainable<void> => {
|
|
46
|
+
return cy.getCookies().then(cookies => {
|
|
47
|
+
const cookiesToClear = cookies.filter(cookie => (type === 'session' ? !cookie.expiry : Boolean(cookie.expiry)));
|
|
48
|
+
|
|
49
|
+
cy.step(`🗑️ CLEAR ${cookiesToClear.length} ${type} cookie(s):`, () => {
|
|
50
|
+
cookiesToClear.forEach(cookie => {
|
|
51
|
+
const info = cookie.expiry ? `expires ${new Date(cookie.expiry * 1000).toISOString()}` : 'session only';
|
|
52
|
+
cy.log(` ... clearing ${cookie.name} (${info})`);
|
|
53
|
+
cy.clearCookie(cookie.name);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}).then(() => undefined);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Logs all available cookies with metadata and values.
|
|
61
|
+
*
|
|
62
|
+
* Intended for interactive debugging when full cookie visibility is needed.
|
|
63
|
+
* @returns {Cypress.Chainable<void>} Cypress chainable resolved when logging is complete.
|
|
64
|
+
*/
|
|
65
|
+
const logCookies = (): Cypress.Chainable<void> => {
|
|
66
|
+
return cy.getCookies().then(cookies => {
|
|
67
|
+
if (cookies.length === 0) {
|
|
68
|
+
cy.log('No cookies found');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
cy.step(`COOKIES REPORT - Total: ${cookies.length}`, () => {
|
|
73
|
+
const sessionCookies = cookies.filter(c => !c.expiry);
|
|
74
|
+
const persistentCookies = cookies.filter(c => Boolean(c.expiry));
|
|
75
|
+
|
|
76
|
+
cy.log(`Session Cookies: ${sessionCookies.length}`);
|
|
77
|
+
cy.log(`Persistent Cookies: ${persistentCookies.length}`);
|
|
78
|
+
|
|
79
|
+
cookies.forEach(cookie => {
|
|
80
|
+
printCookieValues(cookie);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}).then(() => undefined);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Logs a specific cookie by name in a detailed format.
|
|
88
|
+
*
|
|
89
|
+
* Intended for interactive debugging when full cookie visibility is needed.
|
|
90
|
+
* @param {string} cookieName Name of the cookie to read and print.
|
|
91
|
+
* @returns {Cypress.Chainable<void>} Cypress chainable resolved when logging is complete.
|
|
92
|
+
*/
|
|
93
|
+
const logCookie = (cookieName: string): Cypress.Chainable<void> => {
|
|
94
|
+
return cy.getCookie(cookieName).then(cookie => {
|
|
95
|
+
if (!cookie) {
|
|
96
|
+
cy.log(`Cookie "${cookieName}" not found`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
printCookieValues(cookie);
|
|
101
|
+
}).then(() => undefined);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Clears Session cookies
|
|
106
|
+
* @returns {Cypress.Chainable<void>} Cypress chainable resolved when logging is complete.
|
|
107
|
+
*/
|
|
108
|
+
const clearSessionCookies = (): Cypress.Chainable<void> => {
|
|
109
|
+
return clearCookiesByType('session').then(() => undefined);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Clears Persistent cookies
|
|
114
|
+
* @returns {Cypress.Chainable<void>} Cypress chainable resolved when logging is complete.
|
|
115
|
+
*/
|
|
116
|
+
const clearPersistentCookies = (): Cypress.Chainable<void> => {
|
|
117
|
+
return clearCookiesByType('persistent').then(() => undefined);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Simulates a browser close by clearing session storage and cookies.
|
|
122
|
+
* Persistent cookies are kept intentionally.
|
|
123
|
+
* @returns {void}
|
|
124
|
+
*/
|
|
125
|
+
const simulateClose = (): void => {
|
|
126
|
+
cy.log('Simulating browser close...');
|
|
127
|
+
|
|
128
|
+
// Clear session storage
|
|
129
|
+
cy.clearAllSessionStorage();
|
|
130
|
+
|
|
131
|
+
// Clear session cookies only
|
|
132
|
+
clearSessionCookies();
|
|
133
|
+
|
|
134
|
+
cy.log('Browser close simulated (session storage and cookies are cleared)');
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resets browser state by clearing all storages and all cookies.
|
|
139
|
+
* Use this when a test needs a fully clean client-side state.
|
|
140
|
+
* @returns {void}
|
|
141
|
+
*/
|
|
142
|
+
const resetState = (): void => {
|
|
143
|
+
cy.log('Reset browser state...');
|
|
144
|
+
|
|
145
|
+
// Clear all storage
|
|
146
|
+
cy.clearAllLocalStorage();
|
|
147
|
+
cy.clearAllSessionStorage();
|
|
148
|
+
|
|
149
|
+
// Clear all cookies
|
|
150
|
+
cy.clearAllCookies();
|
|
151
|
+
|
|
152
|
+
cy.log('Browser reset is done (all storages and cookies cleared)');
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Logs all sessionStorage entries grouped by origin.
|
|
157
|
+
* Intended for interactive debugging and logs full values.
|
|
158
|
+
* @returns {Cypress.Chainable<void>} Cypress chainable resolved when logging is complete.
|
|
159
|
+
*/
|
|
160
|
+
const logSessionStorage = (): Cypress.Chainable<void> => {
|
|
161
|
+
return cy.getAllSessionStorage().then(session => {
|
|
162
|
+
cy.log(`sessionStorage: ${JSON.stringify(session)}`);
|
|
163
|
+
}).then(() => undefined);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Logs all localStorage entries grouped by origin.
|
|
168
|
+
* Intended for interactive debugging and logs full values.
|
|
169
|
+
* @returns {Cypress.Chainable<void>} Cypress chainable resolved when logging is complete.
|
|
170
|
+
*/
|
|
171
|
+
const logLocalStorage = (): Cypress.Chainable<void> => {
|
|
172
|
+
return cy.getAllLocalStorage().then(local => {
|
|
173
|
+
cy.log(`localStorage: ${JSON.stringify(local)}`);
|
|
174
|
+
}).then(() => undefined);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const BrowserHelper = {
|
|
178
|
+
logCookies,
|
|
179
|
+
logCookie,
|
|
180
|
+
logSessionStorage,
|
|
181
|
+
logLocalStorage,
|
|
182
|
+
clearSessionCookies,
|
|
183
|
+
clearPersistentCookies,
|
|
184
|
+
simulateClose,
|
|
185
|
+
resetState
|
|
186
|
+
};
|