diff --git a/src/actions/getCookies.ts b/src/actions/getCookies.ts index 3c3519cd..3915580c 100644 --- a/src/actions/getCookies.ts +++ b/src/actions/getCookies.ts @@ -5,6 +5,8 @@ import {assertValueIsDefined} from '../utils/asserts'; import type {Cookie} from '../types/internal'; +const msInSecond = 1_000; + /** * Returns page's cookies with the specified cookies parameters. * If there are no cookies parameters, returns all the cookies. @@ -24,8 +26,15 @@ export const getCookies = async ( await step( logMessage, async () => { - const page = getPlaywrightPage(); - const allCookies = await page.context().cookies(page.url()); + const playwrightPage = getPlaywrightPage(); + const playwrightCookies = await playwrightPage.context().cookies(playwrightPage.url()); + + // Playwright's `cookies` returns `expires` in unix time in seconds (`-1` for session cookies), + // while `Cookie` type uses milliseconds (as in `Date.now()`). + const allCookies: readonly Cookie[] = playwrightCookies.map(({expires, ...cookie}) => ({ + ...cookie, + ...(expires !== -1 ? {expires: expires * msInSecond} : undefined), + })); if (parameters.length === 0) { cookies = allCookies; diff --git a/src/actions/setCookies.ts b/src/actions/setCookies.ts index 48a02f96..e18c6a54 100644 --- a/src/actions/setCookies.ts +++ b/src/actions/setCookies.ts @@ -4,6 +4,8 @@ import {getPlaywrightPage} from '../useContext'; import type {Cookie} from '../types/internal'; +const msInSecond = 1_000; + /** * Set cookies with the specified cookies parameters. */ @@ -15,7 +17,14 @@ export const setCookies = (cookies: readonly Cookie[]): Promise => const browserContext = page.context(); - await browserContext.addCookies(cookies); + // Playwright's `addCookies` expects `expires` in unix time in seconds, + // while `Cookie` type uses milliseconds (as in `Date.now()`). + const playwrightCookies = cookies.map(({expires, ...cookie}) => ({ + ...cookie, + ...(expires !== undefined ? {expires: Math.round(expires / msInSecond)} : undefined), + })); + + await browserContext.addCookies(playwrightCookies); }, {payload: {cookies}, type: LogEventType.InternalAction}, ); diff --git a/src/types/http/cookie.ts b/src/types/http/cookie.ts index 7eefa662..0a6ac1f4 100644 --- a/src/types/http/cookie.ts +++ b/src/types/http/cookie.ts @@ -5,6 +5,9 @@ import type {Brand} from '../brand'; */ export type Cookie = Readonly<{ domain?: string; + /** + * Unix time in milliseconds (as in `Date.now()`). + */ expires?: number; httpOnly?: boolean; name: string; diff --git a/src/types/http/http.ts b/src/types/http/http.ts index 04556962..6e9c78ff 100644 --- a/src/types/http/http.ts +++ b/src/types/http/http.ts @@ -41,7 +41,7 @@ export type MapOptions = Readonly<{ * HTTP method. */ export type Method = - 'CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'TRACE'; + 'CONNECT' | 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT' | 'QUERY' | 'TRACE'; /** * Object with query (search) part of the url, or query string itself. diff --git a/src/utils/index.ts b/src/utils/index.ts index e78e2f41..2e94b76d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -44,8 +44,10 @@ export {deepMerge, getEntries, getKeys, setReadonlyProperty} from './object'; export {createPageObjectsFromMultiLocator} from './pageObjects'; export { getCodeReport, + getFullStepDefinition, getLinesIndexes, getScenarioReference, + getStepReference, getTestReference, parseMaybeEmptyValueAsJson, parseTest, diff --git a/src/utils/parse/codeReport/fillTestErrors.ts b/src/utils/parse/codeReport/fillTestErrors.ts index 1ded7204..b1ddd14f 100644 --- a/src/utils/parse/codeReport/fillTestErrors.ts +++ b/src/utils/parse/codeReport/fillTestErrors.ts @@ -1,6 +1,7 @@ import {getScenarioReference} from './getScenarioReference'; import {getScenarioStepsWithReference} from './getScenarioStepsWithReference'; import {getStepComparisonErrors} from './getStepComparisonErrors'; +import {getStepReference} from './getStepReference'; import {getTestReference} from './getTestReference'; import {getTestStepsWithReference} from './getTestStepsWithReference'; @@ -28,17 +29,21 @@ export const fillTestErrors = ( for (const step of scenario.steps) { if (step.definition === '') { scenarioHasError = true; - errors.push( - `Step in ${scenario.featurePath}:${step.lineNumber + 1}:${step.column + 1} (in ${scenarioReference}) has no definition.`, + + const reference = getStepReference( + {column: step.column + 1, line: step.lineNumber + 1}, + scenario.featurePath, ); + + errors.push(`Step ${reference} (in ${scenarioReference}) has no definition.`); } } for (const step of test.steps) { if (step.definition === undefined || step.definition === '') { - errors.push( - `Step in ${test.path}:${step.line}:${step.column} (in ${testReference}) has no definition.`, - ); + const reference = getStepReference(step, test.path); + + errors.push(`Step ${reference} (in ${testReference}) has no definition.`); } } diff --git a/src/utils/parse/codeReport/getFullStepDefinition.ts b/src/utils/parse/codeReport/getFullStepDefinition.ts new file mode 100644 index 00000000..82887e60 --- /dev/null +++ b/src/utils/parse/codeReport/getFullStepDefinition.ts @@ -0,0 +1,13 @@ +type Step = Readonly<{ + definition: string | undefined; + kind: string; +}>; + +/** + * Get full definition of any step. + */ +export const getFullStepDefinition = ({definition, kind}: Step, count: number = 1): string => { + const fullDefinition = `"${kind}${definition === undefined || definition === '' ? '' : ` ${definition}`}"`; + + return count > 1 ? `${fullDefinition} (occurrence ${count})` : fullDefinition; +}; diff --git a/src/utils/parse/codeReport/getScenarioStepsWithReference.ts b/src/utils/parse/codeReport/getScenarioStepsWithReference.ts index 5e40aaff..45a9315c 100644 --- a/src/utils/parse/codeReport/getScenarioStepsWithReference.ts +++ b/src/utils/parse/codeReport/getScenarioStepsWithReference.ts @@ -1,4 +1,6 @@ +import {getFullStepDefinition} from './getFullStepDefinition'; import {getScenarioReference} from './getScenarioReference'; +import {getStepReference} from './getStepReference'; import type {ScenarioReport, StepWithReference} from '../../../types/internal'; @@ -15,17 +17,20 @@ export const getScenarioStepsWithReference = ( const stepsHash: Record = Object.create(null) as {}; for (const step of scenario.steps) { - const fullDefinition = `${step.kind} ${step.definition}`; + const fullDefinition = getFullStepDefinition(step); stepsHash[fullDefinition] = stepsHash[fullDefinition] === undefined ? 1 : stepsHash[fullDefinition] + 1; const count = stepsHash[fullDefinition]; - const reference = `in ${scenario.featurePath}:${step.lineNumber + 1}:${step.column + 1} (in ${scenarioReference})`; + const reference = getStepReference( + {column: step.column + 1, line: step.lineNumber + 1}, + scenario.featurePath, + ); steps.push({ - key: count === 1 ? `"${fullDefinition}"` : `"${fullDefinition}" (occurrence ${count})`, - reference, + key: getFullStepDefinition(step, count), + reference: `${reference} (in ${scenarioReference})`, }); } diff --git a/src/utils/parse/codeReport/getStepReference.ts b/src/utils/parse/codeReport/getStepReference.ts new file mode 100644 index 00000000..c493b5f1 --- /dev/null +++ b/src/utils/parse/codeReport/getStepReference.ts @@ -0,0 +1,10 @@ +type Step = Readonly<{ + column: number; + line: number; +}>; + +/** + * Get step reference (without step definition). + */ +export const getStepReference = ({column, line}: Step, path: string): string => + `in ${path}:${line}:${column}`; diff --git a/src/utils/parse/codeReport/getTestStepsWithReference.ts b/src/utils/parse/codeReport/getTestStepsWithReference.ts index 6efaa00b..8c4844c2 100644 --- a/src/utils/parse/codeReport/getTestStepsWithReference.ts +++ b/src/utils/parse/codeReport/getTestStepsWithReference.ts @@ -1,3 +1,6 @@ +import {getFullStepDefinition} from './getFullStepDefinition'; +import {getStepReference} from './getStepReference'; + import type {StepWithReference, TestReport} from '../../../types/internal'; /** @@ -13,16 +16,16 @@ export const getTestStepsWithReference = (test: TestReport): readonly StepWithRe continue; } - const fullDefinition = `${step.kind} ${step.definition}`; + const fullDefinition = getFullStepDefinition(step); stepsHash[fullDefinition] = stepsHash[fullDefinition] === undefined ? 1 : stepsHash[fullDefinition] + 1; const count = stepsHash[fullDefinition]; - const reference = `in ${test.path}:${step.line}:${step.column}`; + const reference = getStepReference(step, test.path); steps.push({ - key: count === 1 ? `"${fullDefinition}"` : `"${fullDefinition}" (occurrence ${count})`, + key: getFullStepDefinition(step, count), reference, }); } diff --git a/src/utils/parse/codeReport/index.ts b/src/utils/parse/codeReport/index.ts index bd95bf9a..05fbd0f6 100644 --- a/src/utils/parse/codeReport/index.ts +++ b/src/utils/parse/codeReport/index.ts @@ -1,3 +1,5 @@ export {getCodeReport} from './getCodeReport'; +export {getFullStepDefinition} from './getFullStepDefinition'; export {getScenarioReference} from './getScenarioReference'; +export {getStepReference} from './getStepReference'; export {getTestReference} from './getTestReference'; diff --git a/src/utils/parse/index.ts b/src/utils/parse/index.ts index 81f0b9da..fca87d8f 100644 --- a/src/utils/parse/index.ts +++ b/src/utils/parse/index.ts @@ -1,4 +1,10 @@ -export {getCodeReport, getScenarioReference, getTestReference} from './codeReport'; +export { + getCodeReport, + getFullStepDefinition, + getScenarioReference, + getStepReference, + getTestReference, +} from './codeReport'; export {parseMaybeEmptyValueAsJson} from './parseMaybeEmptyValueAsJson'; export {getLinesIndexes, parseTest, ParseTestError} from './parseTest'; export {parseValueAsJsonIfNeeded} from './parseValueAsJsonIfNeeded';