@cronn/playwright-utils 0.10.0 → 0.11.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/dist/index.d.mts CHANGED
@@ -618,9 +618,9 @@ export declare function getCspHeader(response: Response | null): Promise<string>
618
618
  */
619
619
  export declare function cspReport(header: string): string;
620
620
  //#endregion
621
- //#region src/security/response-header-report.d.ts
621
+ //#region src/security/response-headers-report.d.ts
622
622
  type ResponseHeaderType = "document" | "api";
623
- interface ResponseHeaderReportOptions {
623
+ interface ResponseHeadersReportOptions {
624
624
  type: ResponseHeaderType;
625
625
  }
626
626
  /**
@@ -643,12 +643,12 @@ export declare function getResponseHeaders(response: Response | APIResponse | nu
643
643
  * const response = await page.goto("/");
644
644
  * const headers = await getResponseHeaders(response);
645
645
  *
646
- * expect(responseHeaderReport(headers, { type: "document" })).toMatchTextFile({
646
+ * expect(responseHeadersReport(headers, { type: "document" })).toMatchTextFile({
647
647
  * fileExtension: "md",
648
648
  * });
649
649
  * ```
650
650
  */
651
- export declare function responseHeaderReport(headers: Record<string, string>, options: ResponseHeaderReportOptions): string;
651
+ export declare function responseHeadersReport(headers: Record<string, string>, options: ResponseHeadersReportOptions): string;
652
652
  //#endregion
653
653
  //#region src/file.d.ts
654
654
  /**
@@ -789,4 +789,4 @@ export declare function maskedValueWithIndex(name: string): (index: number) => s
789
789
  */
790
790
  type PlaywrightTarget = Page | Locator;
791
791
  //#endregion
792
- export type { ConsoleMessageFilter, ConsoleMessageLevel, CspFixtures, CspViolation, ExtendedLocator, FormFields, HttpMethod, PlaywrightTarget, ResponseHandler, ResponseHeaderReportOptions, ResponseHeaderType, RoleLocators, RouteFilter };
792
+ export type { ConsoleMessageFilter, ConsoleMessageLevel, CspFixtures, CspViolation, ExtendedLocator, FormFields, HttpMethod, PlaywrightTarget, ResponseHandler, ResponseHeaderType, ResponseHeadersReportOptions, RoleLocators, RouteFilter };
package/dist/index.mjs CHANGED
@@ -1028,7 +1028,7 @@ function formatValues(values) {
1028
1028
  return codeOrNone(values.join(" "));
1029
1029
  }
1030
1030
  //#endregion
1031
- //#region src/security/response-header-report.ts
1031
+ //#region src/security/response-headers-report.ts
1032
1032
  const ACTION_ORDER = [
1033
1033
  "add",
1034
1034
  "change",
@@ -1036,32 +1036,35 @@ const ACTION_ORDER = [
1036
1036
  ];
1037
1037
  const MIN_HSTS_MAX_AGE = 31536e3;
1038
1038
  const PERMISSIONS_POLICY = "geolocation=(), microphone=(), camera=()";
1039
- const RECOMMENDED_COMMON_HEADERS = [
1040
- { name: "cache-control" },
1041
- { name: "content-security-policy" },
1042
- { name: "content-type" },
1043
- {
1044
- name: "cross-origin-opener-policy",
1045
- recommended: "same-origin"
1046
- },
1047
- {
1048
- name: "cross-origin-resource-policy",
1049
- recommended: "same-origin"
1050
- },
1051
- {
1052
- name: "strict-transport-security",
1053
- recommended: `max-age=${MIN_HSTS_MAX_AGE}; includeSubDomains`,
1054
- isAccepted: isStrictHsts
1055
- },
1056
- {
1057
- name: "x-content-type-options",
1058
- recommended: "nosniff"
1059
- },
1060
- { name: "x-robots-tag" }
1061
- ];
1039
+ const API_CSP = "frame-ancestors 'none'";
1040
+ function recommendedCommonHeaders(csp) {
1041
+ return [
1042
+ { name: "cache-control" },
1043
+ csp,
1044
+ { name: "content-type" },
1045
+ {
1046
+ name: "cross-origin-opener-policy",
1047
+ recommended: "same-origin"
1048
+ },
1049
+ {
1050
+ name: "cross-origin-resource-policy",
1051
+ recommended: "same-origin"
1052
+ },
1053
+ {
1054
+ name: "strict-transport-security",
1055
+ recommended: `max-age=${MIN_HSTS_MAX_AGE}; includeSubDomains`,
1056
+ isAccepted: isStrictHsts
1057
+ },
1058
+ {
1059
+ name: "x-content-type-options",
1060
+ recommended: "nosniff"
1061
+ },
1062
+ { name: "x-robots-tag" }
1063
+ ];
1064
+ }
1062
1065
  const DISCOURAGED_COMMON_HEADERS = ["access-control-allow-origin"];
1063
1066
  const RECOMMENDED_DOCUMENT_HEADERS = [
1064
- ...RECOMMENDED_COMMON_HEADERS,
1067
+ ...recommendedCommonHeaders({ name: "content-security-policy" }),
1065
1068
  {
1066
1069
  name: "permissions-policy",
1067
1070
  recommended: PERMISSIONS_POLICY,
@@ -1076,10 +1079,15 @@ const RECOMMENDED_DOCUMENT_HEADERS = [
1076
1079
  recommended: "DENY"
1077
1080
  }
1078
1081
  ];
1082
+ const RECOMMENDED_API_HEADERS = recommendedCommonHeaders({
1083
+ name: "content-security-policy",
1084
+ recommended: API_CSP,
1085
+ isAccepted: hasApiCspDirectives
1086
+ });
1079
1087
  const DISCOURAGED_DOCUMENT_HEADERS = [...DISCOURAGED_COMMON_HEADERS, "x-xss-protection"];
1080
1088
  const RECOMMENDED_HEADERS = {
1081
1089
  document: RECOMMENDED_DOCUMENT_HEADERS,
1082
- api: RECOMMENDED_COMMON_HEADERS
1090
+ api: RECOMMENDED_API_HEADERS
1083
1091
  };
1084
1092
  const DISCOURAGED_HEADERS = {
1085
1093
  document: DISCOURAGED_DOCUMENT_HEADERS,
@@ -1108,12 +1116,12 @@ async function getResponseHeaders(response) {
1108
1116
  * const response = await page.goto("/");
1109
1117
  * const headers = await getResponseHeaders(response);
1110
1118
  *
1111
- * expect(responseHeaderReport(headers, { type: "document" })).toMatchTextFile({
1119
+ * expect(responseHeadersReport(headers, { type: "document" })).toMatchTextFile({
1112
1120
  * fileExtension: "md",
1113
1121
  * });
1114
1122
  * ```
1115
1123
  */
1116
- function responseHeaderReport(headers, options) {
1124
+ function responseHeadersReport(headers, options) {
1117
1125
  const recommendedHeaders = RECOMMENDED_HEADERS[options.type];
1118
1126
  const discouragedHeaders = DISCOURAGED_HEADERS[options.type];
1119
1127
  const headersByName = new Map(Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value]));
@@ -1129,7 +1137,7 @@ function responseHeaderReport(headers, options) {
1129
1137
  action: "remove",
1130
1138
  message: "Remove header"
1131
1139
  }))].sort((a, b) => ACTION_ORDER.indexOf(a.action) - ACTION_ORDER.indexOf(b.action));
1132
- return markdownDocument("Response Header Report", [{
1140
+ return markdownDocument("Response Headers Report", [{
1133
1141
  heading: "Detected Headers",
1134
1142
  content: markdownTableOrNone(["Header", "Value"], detectedHeaders.map((header) => [code(header.name), formatValue(header)]))
1135
1143
  }, {
@@ -1164,6 +1172,15 @@ function isStrictHsts(value) {
1164
1172
  return maxAge !== void 0 && Number(maxAge) >= MIN_HSTS_MAX_AGE && directives.includes("includesubdomains");
1165
1173
  }
1166
1174
  /**
1175
+ * Accepts a policy if it contains the recommended directives, so stricter
1176
+ * policies with further directives like `default-src 'none'` are accepted as
1177
+ * well.
1178
+ */
1179
+ function hasApiCspDirectives(value) {
1180
+ const actualDirectives = normalizeValue(value).split("; ");
1181
+ return normalizeValue(API_CSP).split("; ").every((directive) => actualDirectives.includes(directive));
1182
+ }
1183
+ /**
1167
1184
  * Accepts a policy if it restricts at least the recommended permissions, so
1168
1185
  * stricter policies restricting further permissions are accepted as well.
1169
1186
  */
@@ -1420,4 +1437,4 @@ function identity(value) {
1420
1437
  return value;
1421
1438
  }
1422
1439
  //#endregion
1423
- export { ConsoleCaptor, CspCaptor, RouteInterceptor, RouteInterceptorFixture, captureConsole, captureCspViolations, createFetchAdapter, cspFixtures, cspReport, extendLocator, formFiller, getCspHeader, getResponseHeaders, hasLogLevel, interceptRoute, isCI, maskBaseURL, maskedValue, maskedValueWithIndex, matchPath, modifyJsonBody, modifyTextBody, resolveFromPackageRoot, responseHeaderReport, roleLocators };
1440
+ export { ConsoleCaptor, CspCaptor, RouteInterceptor, RouteInterceptorFixture, captureConsole, captureCspViolations, createFetchAdapter, cspFixtures, cspReport, extendLocator, formFiller, getCspHeader, getResponseHeaders, hasLogLevel, interceptRoute, isCI, maskBaseURL, maskedValue, maskedValueWithIndex, matchPath, modifyJsonBody, modifyTextBody, resolveFromPackageRoot, responseHeadersReport, roleLocators };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cronn/playwright-utils",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Utilities for writing tests with Playwright",
5
5
  "keywords": [
6
6
  "playwright",
@@ -37,25 +37,25 @@
37
37
  "@changesets/cli": "3.0.3",
38
38
  "@cronn/element-snapshot": "0.32.1",
39
39
  "@cronn/playwright-file-snapshots": "2.2.2",
40
- "@playwright/test": "1.63.0",
41
- "@types/node": "24.19.1",
40
+ "@playwright/test": "1.64.0",
41
+ "@types/node": "24.19.2",
42
42
  "@typescript/native": "npm:typescript@^7.0.2",
43
43
  "@vitest/coverage-v8": "5.0.3",
44
44
  "eslint": "10.12.0",
45
45
  "eslint-config-prettier": "10.1.8",
46
46
  "eslint-plugin-check-file": "3.3.2",
47
47
  "eslint-plugin-unused-imports": "4.4.1",
48
- "knip": "6.39.0",
48
+ "knip": "6.41.0",
49
49
  "oxc-minify": "0.153.0",
50
50
  "oxfmt": "0.72.0",
51
51
  "publint": "0.3.25",
52
52
  "rimraf": "6.1.3",
53
- "tsdown": "0.23.0",
53
+ "tsdown": "0.23.1",
54
54
  "turbo": "2.11.7",
55
55
  "typescript": "npm:@typescript/typescript6@^6.0.2",
56
56
  "typescript-eslint": "8.71.1",
57
57
  "vitepress": "2.0.0-alpha.20",
58
- "vitepress-plugin-group-icons": "1.7.6",
58
+ "vitepress-plugin-group-icons": "1.7.7",
59
59
  "vitest": "5.0.3"
60
60
  },
61
61
  "peerDependencies": {