/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-namespace */ // Load type definitions that come with Cypress module /// import {ApolloClient, ApolloQueryResult, FetchResult, MutationOptions, QueryOptions} from '@apollo/client/core'; import {DocumentNode} from '@apollo/client/core'; import gql from 'graphql-tag'; import {FieldNode, getOperationAST, print} from 'graphql'; declare global { namespace Cypress { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Chainable { apollo(options: ApolloOptions): Chainable | FetchResult> } } } export type FileQueryOptions = Partial & { queryFile?: string; sourcePackage?: string } export type FileMutationOptions = Partial & { mutationFile?: string; sourcePackage?: string } export type ApolloOptions = (QueryOptions | MutationOptions | FileQueryOptions | FileMutationOptions) & Partial; function isQuery(options: ApolloOptions): options is QueryOptions { return (options).query !== undefined; } function isQueryFile(options: ApolloOptions): options is FileQueryOptions { return (options).queryFile !== undefined; } function isMutationFile(options: ApolloOptions): options is FileMutationOptions { return (options).mutationFile !== undefined; } function getOperationLabel(doc: DocumentNode, opType: 'Query' | 'Mutation'): string { const opDef = getOperationAST(doc); if (opDef?.name?.value) { return `[${opType}] ${opDef.name.value}`; } // Anonymous operation: traverse up to 2 selection levels for a meaningful label const firstSel = opDef?.selectionSet?.selections?.[0]; if (firstSel?.kind === 'Field') { const firstName = (firstSel as FieldNode).name.value; const secondSel = (firstSel as FieldNode).selectionSet?.selections?.[0]; if (secondSel?.kind === 'Field') { return `[${opType}] ${firstName} › ${(secondSel as FieldNode).name.value}`; } return `[${opType}] ${firstName}`; } return `[${opType}]`; } function getQueryBody(doc: DocumentNode): string { return doc?.loc?.source?.body ?? print(doc); } // eslint-disable-next-line default-param-last, @typescript-eslint/no-shadow export const apollo = function (apollo: ApolloClient = this.currentApolloClient, options: ApolloOptions): void { let result : ApolloQueryResult | FetchResult; let logger : Cypress.Log; let duration: number; const optionsWithDefaultCache: ApolloOptions = {fetchPolicy: 'no-cache', ...options}; if (!apollo) { cy.apolloClient().apollo(optionsWithDefaultCache); } else if (isQueryFile(optionsWithDefaultCache)) { const {queryFile, sourcePackage, ...apolloOptions} = optionsWithDefaultCache as FileQueryOptions & Partial; cy.fixture(queryFile).then(content => { const fileLabel = sourcePackage ? `${queryFile} @ ${sourcePackage}` : queryFile; cy.apollo({query: gql(content), ...apolloOptions, _sourceFile: fileLabel} as ApolloOptions); }); } else if (isMutationFile(optionsWithDefaultCache)) { const {mutationFile, sourcePackage, ...apolloOptions} = optionsWithDefaultCache as FileMutationOptions & Partial; cy.fixture(mutationFile).then(content => { const fileLabel = sourcePackage ? `${mutationFile} @ ${sourcePackage}` : mutationFile; cy.apollo({mutation: gql(content), ...apolloOptions, _sourceFile: fileLabel} as ApolloOptions); }); } else { const {log = true, ...apolloOptions} = optionsWithDefaultCache; const doc = isQuery(apolloOptions) ? (apolloOptions as QueryOptions).query : (apolloOptions as MutationOptions).mutation; const opType = isQuery(apolloOptions) ? 'Query' : 'Mutation'; const operationLabel = getOperationLabel(doc, opType); const queryBody = getQueryBody(doc); const variables = (apolloOptions as any).variables; const sourceLabel = (optionsWithDefaultCache as any)._sourceFile ? ` (${(optionsWithDefaultCache as any)._sourceFile})` : ''; const variablesLabel = variables && Object.keys(variables).length > 0 ? ` — ${JSON.stringify(variables)}` : ''; if (log) { logger = Cypress.log({ autoEnd: false, name: 'apollo', displayName: 'apollo', message: `${operationLabel}${sourceLabel}${variablesLabel}`, consoleProps: () => { const errors = (result as any)?.errors ?? (result as any)?.graphQLErrors ?? null; const isCaughtError = result instanceof Error; const hasErrors = (errors?.length > 0) || isCaughtError; return { Operation: operationLabel, Variables: variables ?? {}, [`${opType} Body`]: queryBody, Duration: duration === undefined ? 'pending' : `${duration}ms`, Status: hasErrors ? `error${isCaughtError ? `: ${(result as unknown as Error).message}` : ''}` : 'success', Data: (result as any)?.data ?? null, Errors: errors, Yielded: result }; } }); } const startTime = Date.now(); cy.wrap({}, {log: false}) .then(() => (isQuery(optionsWithDefaultCache) ? apollo.query(optionsWithDefaultCache).catch(error => { cy.log(`Caught GraphQL query error: ${(error as any)?.message ?? JSON.stringify(error)}`); return error; }) : apollo.mutate(optionsWithDefaultCache).catch(error => { cy.log(`Caught GraphQL mutation error: ${(error as any)?.message ?? JSON.stringify(error)}`); return error; })) .then(r => { result = r; duration = Date.now() - startTime; if (logger) { const errors = (r as any)?.errors ?? (r as any)?.graphQLErrors; const hasErrors = (r instanceof Error) || (errors?.length > 0); const prefix = hasErrors ? '❌ ' : '✅ '; logger.set('message', `${prefix}${operationLabel}${sourceLabel}${variablesLabel}`); } logger?.end(); return r; }) ); } };