uidex 0.0.1 → 0.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/README.md +103 -152
- package/claude/audit-command.md +16 -0
- package/claude/rules.md +88 -0
- package/dist/core/index.cjs +3217 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +331 -0
- package/dist/core/index.d.ts +331 -0
- package/dist/core/index.global.js +3199 -0
- package/dist/core/index.global.js.map +1 -0
- package/dist/core/index.js +3174 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/style.css +964 -0
- package/dist/index.cjs +3288 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +74 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +3251 -0
- package/dist/index.js.map +1 -0
- package/dist/playwright/index.cjs +72 -0
- package/dist/playwright/index.cjs.map +1 -0
- package/dist/playwright/index.d.cts +62 -0
- package/dist/playwright/index.d.ts +62 -0
- package/dist/playwright/index.js +39 -0
- package/dist/playwright/index.js.map +1 -0
- package/dist/playwright/reporter.cjs +88 -0
- package/dist/playwright/reporter.cjs.map +1 -0
- package/dist/playwright/reporter.d.cts +24 -0
- package/dist/playwright/reporter.d.ts +24 -0
- package/dist/playwright/reporter.js +57 -0
- package/dist/playwright/reporter.js.map +1 -0
- package/dist/react/index.cjs +3288 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +74 -0
- package/dist/react/index.d.ts +74 -0
- package/dist/react/index.js +3251 -0
- package/dist/react/index.js.map +1 -0
- package/dist/scripts/cli.cjs +1167 -0
- package/package.json +106 -7
- package/uidex.schema.json +93 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Locator, Page } from '@playwright/test';
|
|
2
|
+
export { expect } from '@playwright/test';
|
|
3
|
+
import * as playwright_test from 'playwright/test';
|
|
4
|
+
|
|
5
|
+
type UidexLocator<T extends string = string> = (id: T) => Locator;
|
|
6
|
+
interface UidexFixtures {
|
|
7
|
+
/**
|
|
8
|
+
* Create a Playwright locator for a uidex-annotated element.
|
|
9
|
+
* Automatically tracks usage for the coverage reporter.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* test('add todo', async ({ uidex }) => {
|
|
14
|
+
* await uidex('todo-input').fill('Buy milk');
|
|
15
|
+
* await uidex('todo-add-button').click();
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
uidex: UidexLocator;
|
|
20
|
+
}
|
|
21
|
+
declare const test: playwright_test.TestType<playwright_test.PlaywrightTestArgs & playwright_test.PlaywrightTestOptions & UidexFixtures, playwright_test.PlaywrightWorkerArgs & playwright_test.PlaywrightWorkerOptions>;
|
|
22
|
+
|
|
23
|
+
/** The data attribute used for uidex component selectors. */
|
|
24
|
+
declare const UIDEX_ATTR = "data-uidex";
|
|
25
|
+
/** Build a CSS selector for a uidex-annotated element. */
|
|
26
|
+
declare function uidexSelector(id: string): string;
|
|
27
|
+
/** Attachment name used to pass coverage data from fixture to reporter. */
|
|
28
|
+
declare const COVERAGE_ATTACHMENT = "uidex-coverage";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a Playwright locator for a uidex-annotated element.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import { uidex } from 'uidex/playwright';
|
|
36
|
+
*
|
|
37
|
+
* test('submit form', async ({ page }) => {
|
|
38
|
+
* await uidex(page, 'submit-btn').click();
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function uidex(page: Page, id: string): Locator;
|
|
43
|
+
/**
|
|
44
|
+
* Create a typed locator factory bound to a Page instance.
|
|
45
|
+
* When used with the generated ComponentId type, provides autocomplete
|
|
46
|
+
* for all annotated component IDs.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { createUidexLocators } from 'uidex/playwright';
|
|
51
|
+
* import type { ComponentId } from './uidex.gen.test';
|
|
52
|
+
*
|
|
53
|
+
* test('checkout flow', async ({ page }) => {
|
|
54
|
+
* const u = createUidexLocators<ComponentId>(page);
|
|
55
|
+
* await u('cart-summary').waitFor(); // autocomplete + type checking
|
|
56
|
+
* await u('checkout-btn').click();
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function createUidexLocators<T extends string = string>(page: Page): (id: T) => Locator;
|
|
61
|
+
|
|
62
|
+
export { COVERAGE_ATTACHMENT, UIDEX_ATTR, type UidexFixtures, type UidexLocator, createUidexLocators, test, uidex, uidexSelector };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/playwright/fixture.ts
|
|
2
|
+
import { test as base, expect } from "@playwright/test";
|
|
3
|
+
var test = base.extend({
|
|
4
|
+
uidex: async ({ page }, use, testInfo) => {
|
|
5
|
+
const used = /* @__PURE__ */ new Set();
|
|
6
|
+
const locator = (id) => {
|
|
7
|
+
used.add(id);
|
|
8
|
+
return page.locator(uidexSelector(id));
|
|
9
|
+
};
|
|
10
|
+
await use(locator);
|
|
11
|
+
await testInfo.attach(COVERAGE_ATTACHMENT, {
|
|
12
|
+
body: JSON.stringify([...used]),
|
|
13
|
+
contentType: "application/json"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// src/playwright/index.ts
|
|
19
|
+
var UIDEX_ATTR = "data-uidex";
|
|
20
|
+
function uidexSelector(id) {
|
|
21
|
+
return `[${UIDEX_ATTR}="${id}"]`;
|
|
22
|
+
}
|
|
23
|
+
var COVERAGE_ATTACHMENT = "uidex-coverage";
|
|
24
|
+
function uidex(page, id) {
|
|
25
|
+
return page.locator(uidexSelector(id));
|
|
26
|
+
}
|
|
27
|
+
function createUidexLocators(page) {
|
|
28
|
+
return (id) => page.locator(uidexSelector(id));
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
COVERAGE_ATTACHMENT,
|
|
32
|
+
UIDEX_ATTR,
|
|
33
|
+
createUidexLocators,
|
|
34
|
+
expect,
|
|
35
|
+
test,
|
|
36
|
+
uidex,
|
|
37
|
+
uidexSelector
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/playwright/fixture.ts","../../src/playwright/index.ts"],"sourcesContent":["import { test as base, expect } from '@playwright/test';\nimport type { Locator } from '@playwright/test';\nimport { uidexSelector, COVERAGE_ATTACHMENT } from './index';\n\ntype UidexLocator<T extends string = string> = (id: T) => Locator;\n\ninterface UidexFixtures {\n /**\n * Create a Playwright locator for a uidex-annotated element.\n * Automatically tracks usage for the coverage reporter.\n *\n * @example\n * ```ts\n * test('add todo', async ({ uidex }) => {\n * await uidex('todo-input').fill('Buy milk');\n * await uidex('todo-add-button').click();\n * });\n * ```\n */\n uidex: UidexLocator;\n}\n\nexport const test = base.extend<UidexFixtures>({\n uidex: async ({ page }, use, testInfo) => {\n const used = new Set<string>();\n\n const locator: UidexLocator = (id: string) => {\n used.add(id);\n return page.locator(uidexSelector(id));\n };\n\n await use(locator);\n\n // Attach coverage data for the reporter to collect\n await testInfo.attach(COVERAGE_ATTACHMENT, {\n body: JSON.stringify([...used]),\n contentType: 'application/json',\n });\n },\n});\n\nexport { expect };\nexport type { UidexLocator, UidexFixtures };\n","import type { Page, Locator } from '@playwright/test';\n\n/** The data attribute used for uidex component selectors. */\nexport const UIDEX_ATTR = 'data-uidex';\n\n/** Build a CSS selector for a uidex-annotated element. */\nexport function uidexSelector(id: string): string {\n return `[${UIDEX_ATTR}=\"${id}\"]`;\n}\n\n/** Attachment name used to pass coverage data from fixture to reporter. */\nexport const COVERAGE_ATTACHMENT = 'uidex-coverage';\n\n// Fixture — provides `uidex` as a Playwright test fixture with coverage tracking\nexport { test, expect } from './fixture';\nexport type { UidexLocator, UidexFixtures } from './fixture';\n\n/**\n * Create a Playwright locator for a uidex-annotated element.\n *\n * @example\n * ```ts\n * import { uidex } from 'uidex/playwright';\n *\n * test('submit form', async ({ page }) => {\n * await uidex(page, 'submit-btn').click();\n * });\n * ```\n */\nexport function uidex(page: Page, id: string): Locator {\n return page.locator(uidexSelector(id));\n}\n\n/**\n * Create a typed locator factory bound to a Page instance.\n * When used with the generated ComponentId type, provides autocomplete\n * for all annotated component IDs.\n *\n * @example\n * ```ts\n * import { createUidexLocators } from 'uidex/playwright';\n * import type { ComponentId } from './uidex.gen.test';\n *\n * test('checkout flow', async ({ page }) => {\n * const u = createUidexLocators<ComponentId>(page);\n * await u('cart-summary').waitFor(); // autocomplete + type checking\n * await u('checkout-btn').click();\n * });\n * ```\n */\nexport function createUidexLocators<T extends string = string>(\n page: Page,\n): (id: T) => Locator {\n return (id: T) => page.locator(uidexSelector(id));\n}\n"],"mappings":";AAAA,SAAS,QAAQ,MAAM,cAAc;AAsB9B,IAAM,OAAO,KAAK,OAAsB;AAAA,EAC7C,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK,aAAa;AACxC,UAAM,OAAO,oBAAI,IAAY;AAE7B,UAAM,UAAwB,CAAC,OAAe;AAC5C,WAAK,IAAI,EAAE;AACX,aAAO,KAAK,QAAQ,cAAc,EAAE,CAAC;AAAA,IACvC;AAEA,UAAM,IAAI,OAAO;AAGjB,UAAM,SAAS,OAAO,qBAAqB;AAAA,MACzC,MAAM,KAAK,UAAU,CAAC,GAAG,IAAI,CAAC;AAAA,MAC9B,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AACF,CAAC;;;ACpCM,IAAM,aAAa;AAGnB,SAAS,cAAc,IAAoB;AAChD,SAAO,IAAI,UAAU,KAAK,EAAE;AAC9B;AAGO,IAAM,sBAAsB;AAkB5B,SAAS,MAAM,MAAY,IAAqB;AACrD,SAAO,KAAK,QAAQ,cAAc,EAAE,CAAC;AACvC;AAmBO,SAAS,oBACd,MACoB;AACpB,SAAO,CAAC,OAAU,KAAK,QAAQ,cAAc,EAAE,CAAC;AAClD;","names":[]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/playwright/reporter.ts
|
|
31
|
+
var reporter_exports = {};
|
|
32
|
+
__export(reporter_exports, {
|
|
33
|
+
default: () => reporter_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(reporter_exports);
|
|
36
|
+
var fs = __toESM(require("fs"), 1);
|
|
37
|
+
var path = __toESM(require("path"), 1);
|
|
38
|
+
|
|
39
|
+
// src/playwright/index.ts
|
|
40
|
+
var COVERAGE_ATTACHMENT = "uidex-coverage";
|
|
41
|
+
|
|
42
|
+
// src/playwright/reporter.ts
|
|
43
|
+
var UidexCoverageReporter = class {
|
|
44
|
+
interacted = /* @__PURE__ */ new Set();
|
|
45
|
+
componentIds;
|
|
46
|
+
outputPath;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.componentIds = options.componentIds ?? [];
|
|
49
|
+
this.outputPath = options.outputPath ?? "uidex-coverage.json";
|
|
50
|
+
}
|
|
51
|
+
onTestEnd(_test, result) {
|
|
52
|
+
for (const attachment of result.attachments) {
|
|
53
|
+
if (attachment.name === COVERAGE_ATTACHMENT && attachment.body) {
|
|
54
|
+
const ids = JSON.parse(attachment.body.toString());
|
|
55
|
+
for (const id of ids) {
|
|
56
|
+
this.interacted.add(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
onEnd(_result) {
|
|
62
|
+
const all = [...this.componentIds];
|
|
63
|
+
const covered = all.filter((id) => this.interacted.has(id)).sort();
|
|
64
|
+
const uncovered = all.filter((id) => !this.interacted.has(id)).sort();
|
|
65
|
+
const total = all.length;
|
|
66
|
+
const percentage = total > 0 ? Math.round(covered.length / total * 100) : 0;
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log(
|
|
69
|
+
`uidex coverage: ${covered.length}/${total} components (${percentage}%)`
|
|
70
|
+
);
|
|
71
|
+
if (uncovered.length > 0) {
|
|
72
|
+
console.log(` uncovered: ${uncovered.join(", ")}`);
|
|
73
|
+
}
|
|
74
|
+
console.log("");
|
|
75
|
+
const report = {
|
|
76
|
+
covered,
|
|
77
|
+
uncovered,
|
|
78
|
+
total,
|
|
79
|
+
percentage
|
|
80
|
+
};
|
|
81
|
+
fs.writeFileSync(
|
|
82
|
+
path.resolve(process.cwd(), this.outputPath),
|
|
83
|
+
JSON.stringify(report, null, 2) + "\n"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var reporter_default = UidexCoverageReporter;
|
|
88
|
+
//# sourceMappingURL=reporter.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/playwright/reporter.ts","../../src/playwright/index.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport type {\n Reporter,\n FullResult,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\nimport { COVERAGE_ATTACHMENT } from './index';\n\nexport interface UidexCoverageOptions {\n /** All known component IDs — pass componentIds from uidex.gen.test.ts */\n componentIds: readonly string[];\n /** Output file path for JSON report (default: \"uidex-coverage.json\") */\n outputPath?: string;\n}\n\nexport interface UidexCoverageReport {\n covered: string[];\n uncovered: string[];\n total: number;\n percentage: number;\n}\n\nclass UidexCoverageReporter implements Reporter {\n private interacted = new Set<string>();\n private componentIds: readonly string[];\n private outputPath: string;\n\n constructor(options: UidexCoverageOptions) {\n this.componentIds = options.componentIds ?? [];\n this.outputPath = options.outputPath ?? 'uidex-coverage.json';\n }\n\n onTestEnd(_test: TestCase, result: TestResult) {\n for (const attachment of result.attachments) {\n if (attachment.name === COVERAGE_ATTACHMENT && attachment.body) {\n const ids: string[] = JSON.parse(attachment.body.toString());\n for (const id of ids) {\n this.interacted.add(id);\n }\n }\n }\n }\n\n onEnd(_result: FullResult) {\n const all = [...this.componentIds];\n const covered = all.filter((id) => this.interacted.has(id)).sort();\n const uncovered = all.filter((id) => !this.interacted.has(id)).sort();\n const total = all.length;\n const percentage =\n total > 0 ? Math.round((covered.length / total) * 100) : 0;\n\n // Console summary\n console.log('');\n console.log(\n `uidex coverage: ${covered.length}/${total} components (${percentage}%)`,\n );\n if (uncovered.length > 0) {\n console.log(` uncovered: ${uncovered.join(', ')}`);\n }\n console.log('');\n\n // JSON report\n const report: UidexCoverageReport = {\n covered,\n uncovered,\n total,\n percentage,\n };\n fs.writeFileSync(\n path.resolve(process.cwd(), this.outputPath),\n JSON.stringify(report, null, 2) + '\\n',\n );\n }\n}\n\nexport default UidexCoverageReporter;\n","import type { Page, Locator } from '@playwright/test';\n\n/** The data attribute used for uidex component selectors. */\nexport const UIDEX_ATTR = 'data-uidex';\n\n/** Build a CSS selector for a uidex-annotated element. */\nexport function uidexSelector(id: string): string {\n return `[${UIDEX_ATTR}=\"${id}\"]`;\n}\n\n/** Attachment name used to pass coverage data from fixture to reporter. */\nexport const COVERAGE_ATTACHMENT = 'uidex-coverage';\n\n// Fixture — provides `uidex` as a Playwright test fixture with coverage tracking\nexport { test, expect } from './fixture';\nexport type { UidexLocator, UidexFixtures } from './fixture';\n\n/**\n * Create a Playwright locator for a uidex-annotated element.\n *\n * @example\n * ```ts\n * import { uidex } from 'uidex/playwright';\n *\n * test('submit form', async ({ page }) => {\n * await uidex(page, 'submit-btn').click();\n * });\n * ```\n */\nexport function uidex(page: Page, id: string): Locator {\n return page.locator(uidexSelector(id));\n}\n\n/**\n * Create a typed locator factory bound to a Page instance.\n * When used with the generated ComponentId type, provides autocomplete\n * for all annotated component IDs.\n *\n * @example\n * ```ts\n * import { createUidexLocators } from 'uidex/playwright';\n * import type { ComponentId } from './uidex.gen.test';\n *\n * test('checkout flow', async ({ page }) => {\n * const u = createUidexLocators<ComponentId>(page);\n * await u('cart-summary').waitFor(); // autocomplete + type checking\n * await u('checkout-btn').click();\n * });\n * ```\n */\nexport function createUidexLocators<T extends string = string>(\n page: Page,\n): (id: T) => Locator {\n return (id: T) => page.locator(uidexSelector(id));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAoB;AACpB,WAAsB;;;ACUf,IAAM,sBAAsB;;;ADanC,IAAM,wBAAN,MAAgD;AAAA,EACtC,aAAa,oBAAI,IAAY;AAAA,EAC7B;AAAA,EACA;AAAA,EAER,YAAY,SAA+B;AACzC,SAAK,eAAe,QAAQ,gBAAgB,CAAC;AAC7C,SAAK,aAAa,QAAQ,cAAc;AAAA,EAC1C;AAAA,EAEA,UAAU,OAAiB,QAAoB;AAC7C,eAAW,cAAc,OAAO,aAAa;AAC3C,UAAI,WAAW,SAAS,uBAAuB,WAAW,MAAM;AAC9D,cAAM,MAAgB,KAAK,MAAM,WAAW,KAAK,SAAS,CAAC;AAC3D,mBAAW,MAAM,KAAK;AACpB,eAAK,WAAW,IAAI,EAAE;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAqB;AACzB,UAAM,MAAM,CAAC,GAAG,KAAK,YAAY;AACjC,UAAM,UAAU,IAAI,OAAO,CAAC,OAAO,KAAK,WAAW,IAAI,EAAE,CAAC,EAAE,KAAK;AACjE,UAAM,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,WAAW,IAAI,EAAE,CAAC,EAAE,KAAK;AACpE,UAAM,QAAQ,IAAI;AAClB,UAAM,aACJ,QAAQ,IAAI,KAAK,MAAO,QAAQ,SAAS,QAAS,GAAG,IAAI;AAG3D,YAAQ,IAAI,EAAE;AACd,YAAQ;AAAA,MACN,mBAAmB,QAAQ,MAAM,IAAI,KAAK,gBAAgB,UAAU;AAAA,IACtE;AACA,QAAI,UAAU,SAAS,GAAG;AACxB,cAAQ,IAAI,gBAAgB,UAAU,KAAK,IAAI,CAAC,EAAE;AAAA,IACpD;AACA,YAAQ,IAAI,EAAE;AAGd,UAAM,SAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,IAAG;AAAA,MACI,aAAQ,QAAQ,IAAI,GAAG,KAAK,UAAU;AAAA,MAC3C,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI;AAAA,IACpC;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Reporter, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
|
+
interface UidexCoverageOptions {
|
|
4
|
+
/** All known component IDs — pass componentIds from uidex.gen.test.ts */
|
|
5
|
+
componentIds: readonly string[];
|
|
6
|
+
/** Output file path for JSON report (default: "uidex-coverage.json") */
|
|
7
|
+
outputPath?: string;
|
|
8
|
+
}
|
|
9
|
+
interface UidexCoverageReport {
|
|
10
|
+
covered: string[];
|
|
11
|
+
uncovered: string[];
|
|
12
|
+
total: number;
|
|
13
|
+
percentage: number;
|
|
14
|
+
}
|
|
15
|
+
declare class UidexCoverageReporter implements Reporter {
|
|
16
|
+
private interacted;
|
|
17
|
+
private componentIds;
|
|
18
|
+
private outputPath;
|
|
19
|
+
constructor(options: UidexCoverageOptions);
|
|
20
|
+
onTestEnd(_test: TestCase, result: TestResult): void;
|
|
21
|
+
onEnd(_result: FullResult): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { type UidexCoverageOptions, type UidexCoverageReport, UidexCoverageReporter as default };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Reporter, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
|
+
interface UidexCoverageOptions {
|
|
4
|
+
/** All known component IDs — pass componentIds from uidex.gen.test.ts */
|
|
5
|
+
componentIds: readonly string[];
|
|
6
|
+
/** Output file path for JSON report (default: "uidex-coverage.json") */
|
|
7
|
+
outputPath?: string;
|
|
8
|
+
}
|
|
9
|
+
interface UidexCoverageReport {
|
|
10
|
+
covered: string[];
|
|
11
|
+
uncovered: string[];
|
|
12
|
+
total: number;
|
|
13
|
+
percentage: number;
|
|
14
|
+
}
|
|
15
|
+
declare class UidexCoverageReporter implements Reporter {
|
|
16
|
+
private interacted;
|
|
17
|
+
private componentIds;
|
|
18
|
+
private outputPath;
|
|
19
|
+
constructor(options: UidexCoverageOptions);
|
|
20
|
+
onTestEnd(_test: TestCase, result: TestResult): void;
|
|
21
|
+
onEnd(_result: FullResult): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { type UidexCoverageOptions, type UidexCoverageReport, UidexCoverageReporter as default };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/playwright/reporter.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
// src/playwright/index.ts
|
|
6
|
+
var COVERAGE_ATTACHMENT = "uidex-coverage";
|
|
7
|
+
|
|
8
|
+
// src/playwright/reporter.ts
|
|
9
|
+
var UidexCoverageReporter = class {
|
|
10
|
+
interacted = /* @__PURE__ */ new Set();
|
|
11
|
+
componentIds;
|
|
12
|
+
outputPath;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.componentIds = options.componentIds ?? [];
|
|
15
|
+
this.outputPath = options.outputPath ?? "uidex-coverage.json";
|
|
16
|
+
}
|
|
17
|
+
onTestEnd(_test, result) {
|
|
18
|
+
for (const attachment of result.attachments) {
|
|
19
|
+
if (attachment.name === COVERAGE_ATTACHMENT && attachment.body) {
|
|
20
|
+
const ids = JSON.parse(attachment.body.toString());
|
|
21
|
+
for (const id of ids) {
|
|
22
|
+
this.interacted.add(id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
onEnd(_result) {
|
|
28
|
+
const all = [...this.componentIds];
|
|
29
|
+
const covered = all.filter((id) => this.interacted.has(id)).sort();
|
|
30
|
+
const uncovered = all.filter((id) => !this.interacted.has(id)).sort();
|
|
31
|
+
const total = all.length;
|
|
32
|
+
const percentage = total > 0 ? Math.round(covered.length / total * 100) : 0;
|
|
33
|
+
console.log("");
|
|
34
|
+
console.log(
|
|
35
|
+
`uidex coverage: ${covered.length}/${total} components (${percentage}%)`
|
|
36
|
+
);
|
|
37
|
+
if (uncovered.length > 0) {
|
|
38
|
+
console.log(` uncovered: ${uncovered.join(", ")}`);
|
|
39
|
+
}
|
|
40
|
+
console.log("");
|
|
41
|
+
const report = {
|
|
42
|
+
covered,
|
|
43
|
+
uncovered,
|
|
44
|
+
total,
|
|
45
|
+
percentage
|
|
46
|
+
};
|
|
47
|
+
fs.writeFileSync(
|
|
48
|
+
path.resolve(process.cwd(), this.outputPath),
|
|
49
|
+
JSON.stringify(report, null, 2) + "\n"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var reporter_default = UidexCoverageReporter;
|
|
54
|
+
export {
|
|
55
|
+
reporter_default as default
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/playwright/reporter.ts","../../src/playwright/index.ts"],"sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport type {\n Reporter,\n FullResult,\n TestCase,\n TestResult,\n} from '@playwright/test/reporter';\nimport { COVERAGE_ATTACHMENT } from './index';\n\nexport interface UidexCoverageOptions {\n /** All known component IDs — pass componentIds from uidex.gen.test.ts */\n componentIds: readonly string[];\n /** Output file path for JSON report (default: \"uidex-coverage.json\") */\n outputPath?: string;\n}\n\nexport interface UidexCoverageReport {\n covered: string[];\n uncovered: string[];\n total: number;\n percentage: number;\n}\n\nclass UidexCoverageReporter implements Reporter {\n private interacted = new Set<string>();\n private componentIds: readonly string[];\n private outputPath: string;\n\n constructor(options: UidexCoverageOptions) {\n this.componentIds = options.componentIds ?? [];\n this.outputPath = options.outputPath ?? 'uidex-coverage.json';\n }\n\n onTestEnd(_test: TestCase, result: TestResult) {\n for (const attachment of result.attachments) {\n if (attachment.name === COVERAGE_ATTACHMENT && attachment.body) {\n const ids: string[] = JSON.parse(attachment.body.toString());\n for (const id of ids) {\n this.interacted.add(id);\n }\n }\n }\n }\n\n onEnd(_result: FullResult) {\n const all = [...this.componentIds];\n const covered = all.filter((id) => this.interacted.has(id)).sort();\n const uncovered = all.filter((id) => !this.interacted.has(id)).sort();\n const total = all.length;\n const percentage =\n total > 0 ? Math.round((covered.length / total) * 100) : 0;\n\n // Console summary\n console.log('');\n console.log(\n `uidex coverage: ${covered.length}/${total} components (${percentage}%)`,\n );\n if (uncovered.length > 0) {\n console.log(` uncovered: ${uncovered.join(', ')}`);\n }\n console.log('');\n\n // JSON report\n const report: UidexCoverageReport = {\n covered,\n uncovered,\n total,\n percentage,\n };\n fs.writeFileSync(\n path.resolve(process.cwd(), this.outputPath),\n JSON.stringify(report, null, 2) + '\\n',\n );\n }\n}\n\nexport default UidexCoverageReporter;\n","import type { Page, Locator } from '@playwright/test';\n\n/** The data attribute used for uidex component selectors. */\nexport const UIDEX_ATTR = 'data-uidex';\n\n/** Build a CSS selector for a uidex-annotated element. */\nexport function uidexSelector(id: string): string {\n return `[${UIDEX_ATTR}=\"${id}\"]`;\n}\n\n/** Attachment name used to pass coverage data from fixture to reporter. */\nexport const COVERAGE_ATTACHMENT = 'uidex-coverage';\n\n// Fixture — provides `uidex` as a Playwright test fixture with coverage tracking\nexport { test, expect } from './fixture';\nexport type { UidexLocator, UidexFixtures } from './fixture';\n\n/**\n * Create a Playwright locator for a uidex-annotated element.\n *\n * @example\n * ```ts\n * import { uidex } from 'uidex/playwright';\n *\n * test('submit form', async ({ page }) => {\n * await uidex(page, 'submit-btn').click();\n * });\n * ```\n */\nexport function uidex(page: Page, id: string): Locator {\n return page.locator(uidexSelector(id));\n}\n\n/**\n * Create a typed locator factory bound to a Page instance.\n * When used with the generated ComponentId type, provides autocomplete\n * for all annotated component IDs.\n *\n * @example\n * ```ts\n * import { createUidexLocators } from 'uidex/playwright';\n * import type { ComponentId } from './uidex.gen.test';\n *\n * test('checkout flow', async ({ page }) => {\n * const u = createUidexLocators<ComponentId>(page);\n * await u('cart-summary').waitFor(); // autocomplete + type checking\n * await u('checkout-btn').click();\n * });\n * ```\n */\nexport function createUidexLocators<T extends string = string>(\n page: Page,\n): (id: T) => Locator {\n return (id: T) => page.locator(uidexSelector(id));\n}\n"],"mappings":";AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;;;ACUf,IAAM,sBAAsB;;;ADanC,IAAM,wBAAN,MAAgD;AAAA,EACtC,aAAa,oBAAI,IAAY;AAAA,EAC7B;AAAA,EACA;AAAA,EAER,YAAY,SAA+B;AACzC,SAAK,eAAe,QAAQ,gBAAgB,CAAC;AAC7C,SAAK,aAAa,QAAQ,cAAc;AAAA,EAC1C;AAAA,EAEA,UAAU,OAAiB,QAAoB;AAC7C,eAAW,cAAc,OAAO,aAAa;AAC3C,UAAI,WAAW,SAAS,uBAAuB,WAAW,MAAM;AAC9D,cAAM,MAAgB,KAAK,MAAM,WAAW,KAAK,SAAS,CAAC;AAC3D,mBAAW,MAAM,KAAK;AACpB,eAAK,WAAW,IAAI,EAAE;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAqB;AACzB,UAAM,MAAM,CAAC,GAAG,KAAK,YAAY;AACjC,UAAM,UAAU,IAAI,OAAO,CAAC,OAAO,KAAK,WAAW,IAAI,EAAE,CAAC,EAAE,KAAK;AACjE,UAAM,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,KAAK,WAAW,IAAI,EAAE,CAAC,EAAE,KAAK;AACpE,UAAM,QAAQ,IAAI;AAClB,UAAM,aACJ,QAAQ,IAAI,KAAK,MAAO,QAAQ,SAAS,QAAS,GAAG,IAAI;AAG3D,YAAQ,IAAI,EAAE;AACd,YAAQ;AAAA,MACN,mBAAmB,QAAQ,MAAM,IAAI,KAAK,gBAAgB,UAAU;AAAA,IACtE;AACA,QAAI,UAAU,SAAS,GAAG;AACxB,cAAQ,IAAI,gBAAgB,UAAU,KAAK,IAAI,CAAC,EAAE;AAAA,IACpD;AACA,YAAQ,IAAI,EAAE;AAGd,UAAM,SAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,IAAG;AAAA,MACI,aAAQ,QAAQ,IAAI,GAAG,KAAK,UAAU;AAAA,MAC3C,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI;AAAA,IACpC;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;","names":[]}
|