create-appraisejs 0.3.1-alpha.1 → 0.4.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/package.json +1 -1
- package/templates/blank/.env.example +2 -2
- package/templates/blank/.prettierrc +13 -13
- package/templates/blank/cucumber.mjs +12 -1
- package/templates/blank/e2e/helpers/test-data.ts +10 -0
- package/templates/blank/next-env.d.ts +6 -6
- package/templates/blank/package-lock.json +2 -2
- package/templates/blank/package.json +1 -1
- package/templates/blank/packages/cucumber-runtime/package.json +2 -1
- package/templates/blank/packages/cucumber-runtime/src/paths.ts +22 -5
- package/templates/blank/packages/locator-picker-companion/package.json +2 -1
- package/templates/blank/prisma/dev.db +0 -0
- package/templates/blank/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
- package/templates/blank/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
- package/templates/blank/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
- package/templates/blank/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
- package/templates/blank/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
- package/templates/blank/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
- package/templates/blank/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
- package/templates/blank/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
- package/templates/blank/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
- package/templates/blank/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
- package/templates/blank/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
- package/templates/blank/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
- package/templates/blank/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
- package/templates/blank/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -1
- package/templates/blank/scripts/setup-env.ts +0 -0
- package/templates/blank/scripts/sync-test-cases.ts +60 -10
- package/templates/blank/src/components/diagram/flow-diagram-node-search.tsx +9 -2
- package/templates/blank/src/components/diagram/flow-diagram-toolbar.tsx +37 -3
- package/templates/blank/src/components/diagram/flow-diagram.test.tsx +225 -0
- package/templates/blank/src/components/diagram/use-flow-diagram-search.ts +2 -0
- package/templates/blank/src/components/diagram/use-flow-diagram.ts +93 -0
- package/templates/blank/src/lib/appraise-test-case-metadata.test.ts +78 -0
- package/templates/blank/src/lib/appraise-test-case-metadata.ts +220 -0
- package/templates/blank/src/lib/automation/automation-path-roots.test.ts +14 -0
- package/templates/blank/src/lib/automation/automation-path-roots.ts +10 -2
- package/templates/blank/src/lib/database-sync.ts +166 -15
- package/templates/blank/src/lib/executor/local-executor-adapter.ts +6 -2
- package/templates/blank/src/lib/feature-file-generator.ts +54 -10
- package/templates/blank/src/lib/gherkin-parser.test.ts +52 -0
- package/templates/blank/src/lib/gherkin-parser.ts +39 -1
- package/templates/blank/src/lib/sync/projected-feature-utils.ts +5 -1
- package/templates/blank/src/lib/sync/sync-pending-counts.test.ts +115 -0
- package/templates/blank/src/lib/sync/sync-pending-counts.ts +108 -13
- package/templates/blank/src/services/test-run/test-run-service.test.ts +10 -0
- package/templates/blank/src/services/test-run/test-run-service.ts +41 -1
- package/templates/starter/.env.example +2 -2
- package/templates/starter/.prettierrc +13 -13
- package/templates/starter/cucumber.mjs +12 -1
- package/templates/starter/e2e/helpers/test-data.ts +10 -0
- package/templates/starter/next-env.d.ts +6 -6
- package/templates/starter/package-lock.json +2 -2
- package/templates/starter/package.json +1 -1
- package/templates/starter/packages/cucumber-runtime/package.json +2 -1
- package/templates/starter/packages/cucumber-runtime/src/paths.ts +22 -5
- package/templates/starter/packages/locator-picker-companion/package.json +2 -1
- package/templates/starter/prisma/dev.db +0 -0
- package/templates/starter/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
- package/templates/starter/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
- package/templates/starter/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
- package/templates/starter/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
- package/templates/starter/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
- package/templates/starter/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
- package/templates/starter/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
- package/templates/starter/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
- package/templates/starter/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
- package/templates/starter/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
- package/templates/starter/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
- package/templates/starter/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
- package/templates/starter/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
- package/templates/starter/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -1
- package/templates/starter/scripts/setup-env.ts +0 -0
- package/templates/starter/scripts/sync-test-cases.ts +60 -10
- package/templates/starter/src/components/diagram/flow-diagram-node-search.tsx +9 -2
- package/templates/starter/src/components/diagram/flow-diagram-toolbar.tsx +37 -3
- package/templates/starter/src/components/diagram/flow-diagram.test.tsx +225 -0
- package/templates/starter/src/components/diagram/use-flow-diagram-search.ts +2 -0
- package/templates/starter/src/components/diagram/use-flow-diagram.ts +93 -0
- package/templates/starter/src/lib/appraise-test-case-metadata.test.ts +78 -0
- package/templates/starter/src/lib/appraise-test-case-metadata.ts +220 -0
- package/templates/starter/src/lib/automation/automation-path-roots.test.ts +14 -0
- package/templates/starter/src/lib/automation/automation-path-roots.ts +10 -2
- package/templates/starter/src/lib/database-sync.ts +166 -15
- package/templates/starter/src/lib/executor/local-executor-adapter.ts +6 -2
- package/templates/starter/src/lib/feature-file-generator.ts +54 -10
- package/templates/starter/src/lib/gherkin-parser.test.ts +52 -0
- package/templates/starter/src/lib/gherkin-parser.ts +39 -1
- package/templates/starter/src/lib/sync/projected-feature-utils.ts +5 -1
- package/templates/starter/src/lib/sync/sync-pending-counts.test.ts +115 -0
- package/templates/starter/src/lib/sync/sync-pending-counts.ts +108 -13
- package/templates/starter/src/services/test-run/test-run-service.test.ts +10 -0
- package/templates/starter/src/services/test-run/test-run-service.ts +41 -1
- package/dist/cli.e2e.test.d.ts +0 -2
- package/dist/cli.e2e.test.d.ts.map +0 -1
- package/dist/cli.e2e.test.js +0 -75
- package/dist/cli.e2e.test.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -65
- package/dist/config.test.js.map +0 -1
- package/dist/copy-template.test.d.ts +0 -2
- package/dist/copy-template.test.d.ts.map +0 -1
- package/dist/copy-template.test.js +0 -71
- package/dist/copy-template.test.js.map +0 -1
- package/dist/download-repo.test.d.ts +0 -2
- package/dist/download-repo.test.d.ts.map +0 -1
- package/dist/download-repo.test.js +0 -16
- package/dist/download-repo.test.js.map +0 -1
- package/dist/install.test.d.ts +0 -2
- package/dist/install.test.d.ts.map +0 -1
- package/dist/install.test.js +0 -120
- package/dist/install.test.js.map +0 -1
- package/dist/prompts.test.d.ts +0 -2
- package/dist/prompts.test.d.ts.map +0 -1
- package/dist/prompts.test.js +0 -58
- package/dist/prompts.test.js.map +0 -1
- package/templates/default/next-env.d.ts +0 -6
- package/templates/default/packages/locator-picker-companion/dist/cli.d.ts +0 -1
- package/templates/default/packages/locator-picker-companion/dist/cli.js +0 -336
- package/templates/default/packages/locator-picker-companion/dist/index.d.ts +0 -3
- package/templates/default/packages/locator-picker-companion/dist/index.js +0 -3
- package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.d.ts +0 -1
- package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.js +0 -660
- package/templates/default/packages/locator-picker-companion/dist/launcher.d.ts +0 -14
- package/templates/default/packages/locator-picker-companion/dist/launcher.js +0 -58
- package/templates/default/packages/locator-picker-companion/dist/selector-generator.d.ts +0 -6
- package/templates/default/packages/locator-picker-companion/dist/selector-generator.js +0 -261
- package/templates/default/packages/locator-picker-companion/dist/session-file.d.ts +0 -30
- package/templates/default/packages/locator-picker-companion/dist/session-file.js +0 -162
- package/templates/default/packages/locator-picker-companion/dist/types.d.ts +0 -31
- package/templates/default/packages/locator-picker-companion/dist/types.js +0 -1
- package/templates/default/prisma/dev.db +0 -0
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { access, readdir, stat } from 'fs/promises'
|
|
2
|
-
import { execa } from 'execa'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
async function pathExists(targetPath) {
|
|
5
|
-
try {
|
|
6
|
-
await access(targetPath)
|
|
7
|
-
return true
|
|
8
|
-
} catch (_a) {
|
|
9
|
-
return false
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
async function getLatestModifiedTime(targetPath) {
|
|
13
|
-
const stats = await stat(targetPath)
|
|
14
|
-
if (!stats.isDirectory()) {
|
|
15
|
-
return stats.mtimeMs
|
|
16
|
-
}
|
|
17
|
-
const entries = await readdir(targetPath, { withFileTypes: true })
|
|
18
|
-
const times = await Promise.all(entries.map(entry => getLatestModifiedTime(path.join(targetPath, entry.name))))
|
|
19
|
-
return Math.max(stats.mtimeMs, ...times)
|
|
20
|
-
}
|
|
21
|
-
export function getLocatorPickerCompanionPaths(repoRoot = process.cwd()) {
|
|
22
|
-
const packageRoot = path.join(repoRoot, 'packages', 'locator-picker-companion')
|
|
23
|
-
return {
|
|
24
|
-
packageRoot,
|
|
25
|
-
sourceRoot: path.join(packageRoot, 'src'),
|
|
26
|
-
distCliPath: path.join(packageRoot, 'dist', 'cli.js'),
|
|
27
|
-
tsconfigPath: path.join(packageRoot, 'tsconfig.json'),
|
|
28
|
-
tscCliPath: path.join(repoRoot, 'node_modules', 'typescript', 'bin', 'tsc'),
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
async function ensureLocatorPickerCompanionBuilt(repoRoot = process.cwd()) {
|
|
32
|
-
const { distCliPath, sourceRoot, tsconfigPath, tscCliPath } = getLocatorPickerCompanionPaths(repoRoot)
|
|
33
|
-
if (await pathExists(distCliPath)) {
|
|
34
|
-
const [distMtime, srcMtime] = await Promise.all([
|
|
35
|
-
getLatestModifiedTime(distCliPath),
|
|
36
|
-
getLatestModifiedTime(sourceRoot),
|
|
37
|
-
])
|
|
38
|
-
if (distMtime >= srcMtime) {
|
|
39
|
-
return distCliPath
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
await execa(process.execPath, [tscCliPath, '-p', tsconfigPath], {
|
|
43
|
-
cwd: repoRoot,
|
|
44
|
-
env: process.env,
|
|
45
|
-
stdio: 'pipe',
|
|
46
|
-
})
|
|
47
|
-
if (!(await pathExists(distCliPath))) {
|
|
48
|
-
throw new Error('Locator picker companion build completed without producing dist/cli.js.')
|
|
49
|
-
}
|
|
50
|
-
return distCliPath
|
|
51
|
-
}
|
|
52
|
-
export async function resolveLocatorPickerCompanionInvocation(cliArgs, repoRoot = process.cwd()) {
|
|
53
|
-
const distCliPath = await ensureLocatorPickerCompanionBuilt(repoRoot)
|
|
54
|
-
return {
|
|
55
|
-
command: process.execPath,
|
|
56
|
-
args: [distCliPath, ...cliArgs],
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
function normalizeText(value) {
|
|
2
|
-
return (value !== null && value !== void 0 ? value : '').replace(/\s+/g, ' ').trim()
|
|
3
|
-
}
|
|
4
|
-
function normalizeRoute(value) {
|
|
5
|
-
if (!value) {
|
|
6
|
-
return '/'
|
|
7
|
-
}
|
|
8
|
-
try {
|
|
9
|
-
return new URL(value).pathname || '/'
|
|
10
|
-
} catch (_a) {
|
|
11
|
-
return value.startsWith('/') ? value : `/${value}`
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
function escapeForCss(value) {
|
|
15
|
-
return value.replace(/[^a-zA-Z0-9_-]/g, match => `\\${match}`)
|
|
16
|
-
}
|
|
17
|
-
function escapeForSelectorText(value) {
|
|
18
|
-
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
|
19
|
-
}
|
|
20
|
-
function isLikelyStableIdentifier(value) {
|
|
21
|
-
if (!value) {
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
24
|
-
const normalized = value.trim()
|
|
25
|
-
if (!normalized || normalized.length > 120) {
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
28
|
-
return !/\d{4,}/.test(normalized) && !/[A-Fa-f0-9]{8,}/.test(normalized)
|
|
29
|
-
}
|
|
30
|
-
function buildCssSelector(snapshot) {
|
|
31
|
-
const parts = [snapshot.tagName]
|
|
32
|
-
if (isLikelyStableIdentifier(snapshot.nameAttribute)) {
|
|
33
|
-
parts.push(`[name="${escapeForSelectorText(snapshot.nameAttribute)}"]`)
|
|
34
|
-
}
|
|
35
|
-
if (isLikelyStableIdentifier(snapshot.typeAttribute)) {
|
|
36
|
-
parts.push(`[type="${escapeForSelectorText(snapshot.typeAttribute)}"]`)
|
|
37
|
-
}
|
|
38
|
-
if (isLikelyStableIdentifier(snapshot.ariaLabel)) {
|
|
39
|
-
parts.push(`[aria-label="${escapeForSelectorText(snapshot.ariaLabel)}"]`)
|
|
40
|
-
}
|
|
41
|
-
if (parts.length === 1 && snapshot.stableClasses.length > 0) {
|
|
42
|
-
parts.push(...snapshot.stableClasses.slice(0, 2).map(className => `.${escapeForCss(className)}`))
|
|
43
|
-
}
|
|
44
|
-
return parts.length > 1 ? `css=${parts.join('')}` : ''
|
|
45
|
-
}
|
|
46
|
-
function buildXPathSelector(snapshot) {
|
|
47
|
-
if (snapshot.id) {
|
|
48
|
-
return `xpath=//*[@id="${snapshot.id.replace(/"/g, '\\"')}"]`
|
|
49
|
-
}
|
|
50
|
-
if (snapshot.text) {
|
|
51
|
-
return `xpath=//${snapshot.tagName}[normalize-space()="${snapshot.text.replace(/"/g, '\\"')}"]`
|
|
52
|
-
}
|
|
53
|
-
return `xpath=//${snapshot.tagName}`
|
|
54
|
-
}
|
|
55
|
-
function buildPrimarySelector(snapshot) {
|
|
56
|
-
if (isLikelyStableIdentifier(snapshot.testAttributeValue)) {
|
|
57
|
-
return {
|
|
58
|
-
selector: `css=[${snapshot.testAttributeName}="${escapeForSelectorText(snapshot.testAttributeValue)}"]`,
|
|
59
|
-
strategy: 'test-id',
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (snapshot.role && snapshot.accessibleName && snapshot.roleNameMatchCount === 1) {
|
|
63
|
-
return {
|
|
64
|
-
selector: `role=${snapshot.role}[name="${escapeForSelectorText(snapshot.accessibleName)}"]`,
|
|
65
|
-
strategy: 'role',
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (snapshot.labelText) {
|
|
69
|
-
return {
|
|
70
|
-
selector: `label="${escapeForSelectorText(snapshot.labelText)}"`,
|
|
71
|
-
strategy: 'label',
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (snapshot.placeholder) {
|
|
75
|
-
return {
|
|
76
|
-
selector: `placeholder="${escapeForSelectorText(snapshot.placeholder)}"`,
|
|
77
|
-
strategy: 'placeholder',
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (isLikelyStableIdentifier(snapshot.id)) {
|
|
81
|
-
return {
|
|
82
|
-
selector: `css=#${escapeForCss(snapshot.id)}`,
|
|
83
|
-
strategy: 'id',
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const cssSelector = buildCssSelector(snapshot)
|
|
87
|
-
if (cssSelector) {
|
|
88
|
-
return {
|
|
89
|
-
selector: cssSelector,
|
|
90
|
-
strategy: 'css',
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
selector: buildXPathSelector(snapshot),
|
|
95
|
-
strategy: 'xpath',
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
async function getElementSnapshot(elementHandle) {
|
|
99
|
-
return elementHandle.evaluate(node => {
|
|
100
|
-
const element = node
|
|
101
|
-
const normalizeText = value => (value !== null && value !== void 0 ? value : '').replace(/\s+/g, ' ').trim()
|
|
102
|
-
const isLikelyStableIdentifier = value => {
|
|
103
|
-
if (!value) {
|
|
104
|
-
return false
|
|
105
|
-
}
|
|
106
|
-
const normalized = value.trim()
|
|
107
|
-
if (!normalized || normalized.length > 120) {
|
|
108
|
-
return false
|
|
109
|
-
}
|
|
110
|
-
return !/\d{4,}/.test(normalized) && !/[A-Fa-f0-9]{8,}/.test(normalized)
|
|
111
|
-
}
|
|
112
|
-
const getLabelText = candidate => {
|
|
113
|
-
if (!['input', 'textarea', 'select'].includes(candidate.tagName.toLowerCase())) {
|
|
114
|
-
return ''
|
|
115
|
-
}
|
|
116
|
-
const input = candidate
|
|
117
|
-
if (input.labels && input.labels.length > 0) {
|
|
118
|
-
return normalizeText(
|
|
119
|
-
Array.from(input.labels)
|
|
120
|
-
.map(label => label.textContent || '')
|
|
121
|
-
.join(' '),
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
return ''
|
|
125
|
-
}
|
|
126
|
-
const getAccessibleName = candidate => {
|
|
127
|
-
const ariaLabel = normalizeText(candidate.getAttribute('aria-label'))
|
|
128
|
-
if (ariaLabel) {
|
|
129
|
-
return ariaLabel
|
|
130
|
-
}
|
|
131
|
-
const labelledBy = normalizeText(candidate.getAttribute('aria-labelledby'))
|
|
132
|
-
if (labelledBy) {
|
|
133
|
-
const text = labelledBy
|
|
134
|
-
.split(/\s+/)
|
|
135
|
-
.map(id => {
|
|
136
|
-
var _a
|
|
137
|
-
return ((_a = document.getElementById(id)) === null || _a === void 0 ? void 0 : _a.textContent) || ''
|
|
138
|
-
})
|
|
139
|
-
.join(' ')
|
|
140
|
-
const normalized = normalizeText(text)
|
|
141
|
-
if (normalized) {
|
|
142
|
-
return normalized
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const labelText = getLabelText(candidate)
|
|
146
|
-
if (labelText) {
|
|
147
|
-
return labelText
|
|
148
|
-
}
|
|
149
|
-
const alt = normalizeText(candidate.getAttribute('alt'))
|
|
150
|
-
if (alt) {
|
|
151
|
-
return alt
|
|
152
|
-
}
|
|
153
|
-
const title = normalizeText(candidate.getAttribute('title'))
|
|
154
|
-
if (title) {
|
|
155
|
-
return title
|
|
156
|
-
}
|
|
157
|
-
const placeholder = normalizeText(candidate.getAttribute('placeholder'))
|
|
158
|
-
if (placeholder) {
|
|
159
|
-
return placeholder
|
|
160
|
-
}
|
|
161
|
-
return normalizeText(candidate.textContent).slice(0, 240)
|
|
162
|
-
}
|
|
163
|
-
const getRole = candidate => {
|
|
164
|
-
const explicitRole = normalizeText(candidate.getAttribute('role'))
|
|
165
|
-
if (explicitRole) {
|
|
166
|
-
return explicitRole
|
|
167
|
-
}
|
|
168
|
-
const tagName = candidate.tagName.toLowerCase()
|
|
169
|
-
if (tagName === 'button') {
|
|
170
|
-
return 'button'
|
|
171
|
-
}
|
|
172
|
-
if (tagName === 'a' && candidate.hasAttribute('href')) {
|
|
173
|
-
return 'link'
|
|
174
|
-
}
|
|
175
|
-
if (tagName === 'textarea') {
|
|
176
|
-
return 'textbox'
|
|
177
|
-
}
|
|
178
|
-
if (tagName === 'select') {
|
|
179
|
-
return 'combobox'
|
|
180
|
-
}
|
|
181
|
-
if (tagName === 'img') {
|
|
182
|
-
return 'img'
|
|
183
|
-
}
|
|
184
|
-
if (tagName === 'input') {
|
|
185
|
-
const type = (candidate.getAttribute('type') || 'text').toLowerCase()
|
|
186
|
-
if (['button', 'submit', 'reset'].includes(type)) {
|
|
187
|
-
return 'button'
|
|
188
|
-
}
|
|
189
|
-
if (type === 'checkbox') {
|
|
190
|
-
return 'checkbox'
|
|
191
|
-
}
|
|
192
|
-
if (type === 'radio') {
|
|
193
|
-
return 'radio'
|
|
194
|
-
}
|
|
195
|
-
return 'textbox'
|
|
196
|
-
}
|
|
197
|
-
return ''
|
|
198
|
-
}
|
|
199
|
-
const role = getRole(element)
|
|
200
|
-
const accessibleName = getAccessibleName(element)
|
|
201
|
-
let roleNameMatchCount = 0
|
|
202
|
-
if (role && accessibleName) {
|
|
203
|
-
for (const candidate of Array.from(document.querySelectorAll('*'))) {
|
|
204
|
-
if (getRole(candidate) !== role) {
|
|
205
|
-
continue
|
|
206
|
-
}
|
|
207
|
-
if (getAccessibleName(candidate) !== accessibleName) {
|
|
208
|
-
continue
|
|
209
|
-
}
|
|
210
|
-
roleNameMatchCount += 1
|
|
211
|
-
if (roleNameMatchCount > 1) {
|
|
212
|
-
break
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
let testAttributeName = ''
|
|
217
|
-
let testAttributeValue = ''
|
|
218
|
-
for (const attributeName of ['data-testid', 'data-test', 'data-qa']) {
|
|
219
|
-
const value = element.getAttribute(attributeName)
|
|
220
|
-
if (isLikelyStableIdentifier(value)) {
|
|
221
|
-
testAttributeName = attributeName
|
|
222
|
-
testAttributeValue = value
|
|
223
|
-
break
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
tagName: element.tagName.toLowerCase(),
|
|
228
|
-
text: normalizeText(element.textContent).slice(0, 240),
|
|
229
|
-
accessibleName,
|
|
230
|
-
labelText: getLabelText(element),
|
|
231
|
-
placeholder: normalizeText(element.getAttribute('placeholder')),
|
|
232
|
-
id: normalizeText(element.getAttribute('id')),
|
|
233
|
-
role,
|
|
234
|
-
roleNameMatchCount,
|
|
235
|
-
stableClasses: Array.from(element.classList || [])
|
|
236
|
-
.filter(className => isLikelyStableIdentifier(className))
|
|
237
|
-
.slice(0, 2),
|
|
238
|
-
nameAttribute: normalizeText(element.getAttribute('name')),
|
|
239
|
-
typeAttribute: normalizeText(element.getAttribute('type')),
|
|
240
|
-
ariaLabel: normalizeText(element.getAttribute('aria-label')),
|
|
241
|
-
testAttributeName,
|
|
242
|
-
testAttributeValue,
|
|
243
|
-
}
|
|
244
|
-
})
|
|
245
|
-
}
|
|
246
|
-
export async function generatePickedLocatorPayload(page, elementHandle) {
|
|
247
|
-
const snapshot = await getElementSnapshot(elementHandle)
|
|
248
|
-
const selection = buildPrimarySelector(snapshot)
|
|
249
|
-
const currentUrl = page.url()
|
|
250
|
-
const pageTitle = normalizeText(await page.title().catch(() => ''))
|
|
251
|
-
return {
|
|
252
|
-
selector: selection.selector,
|
|
253
|
-
strategy: selection.strategy,
|
|
254
|
-
currentUrl,
|
|
255
|
-
pathname: normalizeRoute(currentUrl),
|
|
256
|
-
pageTitle,
|
|
257
|
-
tagName: snapshot.tagName,
|
|
258
|
-
text: snapshot.text || undefined,
|
|
259
|
-
accessibleName: snapshot.accessibleName || undefined,
|
|
260
|
-
}
|
|
261
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { CompanionSessionFile } from './types.js'
|
|
2
|
-
export declare function getLocatorPickerRootDir(repoRoot?: string): string
|
|
3
|
-
export declare function getLocatorPickerSessionsDir(repoRoot?: string): string
|
|
4
|
-
export declare function getLocatorPickerProfilesDir(repoRoot?: string): string
|
|
5
|
-
export declare function getLocatorPickerLogsDir(repoRoot?: string): string
|
|
6
|
-
export declare function getLocatorPickerProfileDir(sessionId: string, repoRoot?: string): string
|
|
7
|
-
export declare function getLocatorPickerRuntimeDir(repoRoot?: string): string
|
|
8
|
-
export declare function getLocatorPickerRuntimeHomeDir(repoRoot?: string): string
|
|
9
|
-
export declare function getLocatorPickerRuntimeEnv(repoRoot?: string, baseEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv
|
|
10
|
-
export declare function getLocatorPickerSessionFilePath(sessionId: string, repoRoot?: string): string
|
|
11
|
-
export declare function getLocatorPickerCrashLogPath(sessionId: string, repoRoot?: string): string
|
|
12
|
-
export declare function removeLocatorPickerProfileDir(sessionId: string, repoRoot?: string): Promise<void>
|
|
13
|
-
export declare function removeLocatorPickerSessionFile(sessionId: string, repoRoot?: string): Promise<void>
|
|
14
|
-
export declare function clearLocatorPickerCrashLogs(repoRoot?: string): Promise<void>
|
|
15
|
-
export declare function createLocatorPickerCrashLog(logFilePath: string): Promise<void>
|
|
16
|
-
export declare function appendLocatorPickerCrashLog(logFilePath: string, message: string): Promise<void>
|
|
17
|
-
export declare function ensureLocatorPickerDirectories(repoRoot?: string): Promise<void>
|
|
18
|
-
export declare function readLocatorPickerSessionFile(sessionFilePath: string): Promise<CompanionSessionFile | null>
|
|
19
|
-
export declare function writeLocatorPickerSessionFile(
|
|
20
|
-
sessionFilePath: string,
|
|
21
|
-
session: Omit<CompanionSessionFile, 'updatedAt'> & {
|
|
22
|
-
updatedAt?: string
|
|
23
|
-
},
|
|
24
|
-
): Promise<CompanionSessionFile>
|
|
25
|
-
export declare function patchLocatorPickerSessionFile(
|
|
26
|
-
sessionFilePath: string,
|
|
27
|
-
patch:
|
|
28
|
-
| Partial<CompanionSessionFile>
|
|
29
|
-
| ((current: CompanionSessionFile) => Partial<CompanionSessionFile> | CompanionSessionFile),
|
|
30
|
-
): Promise<CompanionSessionFile | null>
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'crypto'
|
|
2
|
-
import { access, appendFile, mkdir, readFile, rename, rm, unlink, writeFile } from 'fs/promises'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
const sessionFileOperationQueue = new Map()
|
|
5
|
-
function withTimestamp(session) {
|
|
6
|
-
var _a
|
|
7
|
-
return Object.assign(Object.assign({}, session), {
|
|
8
|
-
updatedAt: (_a = session.updatedAt) !== null && _a !== void 0 ? _a : new Date().toISOString(),
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
export function getLocatorPickerRootDir(repoRoot = process.cwd()) {
|
|
12
|
-
return path.join(repoRoot, '.tmp', 'locator-picker')
|
|
13
|
-
}
|
|
14
|
-
export function getLocatorPickerSessionsDir(repoRoot = process.cwd()) {
|
|
15
|
-
return path.join(getLocatorPickerRootDir(repoRoot), 'sessions')
|
|
16
|
-
}
|
|
17
|
-
export function getLocatorPickerProfilesDir(repoRoot = process.cwd()) {
|
|
18
|
-
return path.join(getLocatorPickerRootDir(repoRoot), 'profiles')
|
|
19
|
-
}
|
|
20
|
-
export function getLocatorPickerLogsDir(repoRoot = process.cwd()) {
|
|
21
|
-
return path.join(getLocatorPickerRootDir(repoRoot), 'logs')
|
|
22
|
-
}
|
|
23
|
-
export function getLocatorPickerProfileDir(sessionId, repoRoot = process.cwd()) {
|
|
24
|
-
return path.join(getLocatorPickerProfilesDir(repoRoot), sessionId)
|
|
25
|
-
}
|
|
26
|
-
export function getLocatorPickerRuntimeDir(repoRoot = process.cwd()) {
|
|
27
|
-
return path.join(getLocatorPickerRootDir(repoRoot), 'runtime')
|
|
28
|
-
}
|
|
29
|
-
export function getLocatorPickerRuntimeHomeDir(repoRoot = process.cwd()) {
|
|
30
|
-
return path.join(getLocatorPickerRuntimeDir(repoRoot), 'home')
|
|
31
|
-
}
|
|
32
|
-
export function getLocatorPickerRuntimeEnv(repoRoot = process.cwd(), baseEnv = process.env) {
|
|
33
|
-
const runtimeDir = getLocatorPickerRuntimeDir(repoRoot)
|
|
34
|
-
const runtimeHomeDir = getLocatorPickerRuntimeHomeDir(repoRoot)
|
|
35
|
-
const tempDir = path.join(runtimeDir, 'tmp')
|
|
36
|
-
const configDir = path.join(runtimeDir, 'config')
|
|
37
|
-
const cacheDir = path.join(runtimeDir, 'cache')
|
|
38
|
-
const appDataDir = path.join(runtimeDir, 'appdata')
|
|
39
|
-
const localAppDataDir = path.join(runtimeDir, 'localappdata')
|
|
40
|
-
return Object.assign(Object.assign({}, baseEnv), {
|
|
41
|
-
HOME: runtimeHomeDir,
|
|
42
|
-
USERPROFILE: runtimeHomeDir,
|
|
43
|
-
APPDATA: appDataDir,
|
|
44
|
-
LOCALAPPDATA: localAppDataDir,
|
|
45
|
-
XDG_CONFIG_HOME: configDir,
|
|
46
|
-
XDG_CACHE_HOME: cacheDir,
|
|
47
|
-
TMPDIR: tempDir,
|
|
48
|
-
TMP: tempDir,
|
|
49
|
-
TEMP: tempDir,
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
export function getLocatorPickerSessionFilePath(sessionId, repoRoot = process.cwd()) {
|
|
53
|
-
return path.join(getLocatorPickerSessionsDir(repoRoot), `${sessionId}.json`)
|
|
54
|
-
}
|
|
55
|
-
export function getLocatorPickerCrashLogPath(sessionId, repoRoot = process.cwd()) {
|
|
56
|
-
return path.join(getLocatorPickerLogsDir(repoRoot), `${sessionId}.log`)
|
|
57
|
-
}
|
|
58
|
-
export async function removeLocatorPickerProfileDir(sessionId, repoRoot = process.cwd()) {
|
|
59
|
-
await rm(getLocatorPickerProfileDir(sessionId, repoRoot), {
|
|
60
|
-
force: true,
|
|
61
|
-
recursive: true,
|
|
62
|
-
}).catch(() => undefined)
|
|
63
|
-
}
|
|
64
|
-
export async function removeLocatorPickerSessionFile(sessionId, repoRoot = process.cwd()) {
|
|
65
|
-
await unlink(getLocatorPickerSessionFilePath(sessionId, repoRoot)).catch(() => undefined)
|
|
66
|
-
}
|
|
67
|
-
export async function clearLocatorPickerCrashLogs(repoRoot = process.cwd()) {
|
|
68
|
-
await rm(getLocatorPickerLogsDir(repoRoot), {
|
|
69
|
-
force: true,
|
|
70
|
-
recursive: true,
|
|
71
|
-
}).catch(() => console.error('Failed to clear locator picker crash logs.'))
|
|
72
|
-
await mkdir(getLocatorPickerLogsDir(repoRoot), { recursive: true })
|
|
73
|
-
}
|
|
74
|
-
export async function createLocatorPickerCrashLog(logFilePath) {
|
|
75
|
-
await mkdir(path.dirname(logFilePath), { recursive: true })
|
|
76
|
-
await writeFile(logFilePath, '', 'utf8')
|
|
77
|
-
}
|
|
78
|
-
export async function appendLocatorPickerCrashLog(logFilePath, message) {
|
|
79
|
-
try {
|
|
80
|
-
await access(logFilePath)
|
|
81
|
-
} catch (_a) {
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
await appendFile(logFilePath, `[${new Date().toISOString()}] ${message}\n`, 'utf8')
|
|
85
|
-
}
|
|
86
|
-
export async function ensureLocatorPickerDirectories(repoRoot = process.cwd()) {
|
|
87
|
-
const runtimeDir = getLocatorPickerRuntimeDir(repoRoot)
|
|
88
|
-
const runtimeHomeDir = getLocatorPickerRuntimeHomeDir(repoRoot)
|
|
89
|
-
const tempDir = path.join(runtimeDir, 'tmp')
|
|
90
|
-
const configDir = path.join(runtimeDir, 'config')
|
|
91
|
-
const cacheDir = path.join(runtimeDir, 'cache')
|
|
92
|
-
const appDataDir = path.join(runtimeDir, 'appdata')
|
|
93
|
-
const localAppDataDir = path.join(runtimeDir, 'localappdata')
|
|
94
|
-
await mkdir(getLocatorPickerSessionsDir(repoRoot), { recursive: true })
|
|
95
|
-
await mkdir(getLocatorPickerProfilesDir(repoRoot), { recursive: true })
|
|
96
|
-
await mkdir(getLocatorPickerLogsDir(repoRoot), { recursive: true })
|
|
97
|
-
await mkdir(runtimeHomeDir, { recursive: true })
|
|
98
|
-
await mkdir(tempDir, { recursive: true })
|
|
99
|
-
await mkdir(configDir, { recursive: true })
|
|
100
|
-
await mkdir(cacheDir, { recursive: true })
|
|
101
|
-
await mkdir(appDataDir, { recursive: true })
|
|
102
|
-
await mkdir(localAppDataDir, { recursive: true })
|
|
103
|
-
}
|
|
104
|
-
export async function readLocatorPickerSessionFile(sessionFilePath) {
|
|
105
|
-
try {
|
|
106
|
-
const raw = await readFile(sessionFilePath, 'utf8')
|
|
107
|
-
return JSON.parse(raw)
|
|
108
|
-
} catch (_a) {
|
|
109
|
-
return null
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
async function enqueueSessionFileOperation(sessionFilePath, operation) {
|
|
113
|
-
var _a
|
|
114
|
-
const previousOperation =
|
|
115
|
-
(_a = sessionFileOperationQueue.get(sessionFilePath)) !== null && _a !== void 0 ? _a : Promise.resolve()
|
|
116
|
-
const nextOperation = previousOperation.catch(() => undefined).then(operation)
|
|
117
|
-
sessionFileOperationQueue.set(sessionFilePath, nextOperation)
|
|
118
|
-
try {
|
|
119
|
-
return await nextOperation
|
|
120
|
-
} finally {
|
|
121
|
-
if (sessionFileOperationQueue.get(sessionFilePath) === nextOperation) {
|
|
122
|
-
sessionFileOperationQueue.delete(sessionFilePath)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async function writeLocatorPickerSessionFileImmediate(sessionFilePath, session) {
|
|
127
|
-
await mkdir(path.dirname(sessionFilePath), { recursive: true })
|
|
128
|
-
const normalized = withTimestamp(session)
|
|
129
|
-
const serialized = `${JSON.stringify(normalized, null, 2)}\n`
|
|
130
|
-
const tempFilePath = `${sessionFilePath}.${process.pid}.${randomUUID()}.tmp`
|
|
131
|
-
try {
|
|
132
|
-
await writeFile(tempFilePath, serialized, 'utf8')
|
|
133
|
-
await rename(tempFilePath, sessionFilePath)
|
|
134
|
-
} catch (error) {
|
|
135
|
-
await unlink(tempFilePath).catch(() => undefined)
|
|
136
|
-
const code = error instanceof Error && 'code' in error ? String(error.code) : ''
|
|
137
|
-
if (process.platform === 'win32' || code === 'EPERM' || code === 'EACCES' || code === 'ENOENT') {
|
|
138
|
-
await writeFile(sessionFilePath, serialized, 'utf8')
|
|
139
|
-
} else {
|
|
140
|
-
throw error
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return normalized
|
|
144
|
-
}
|
|
145
|
-
export async function writeLocatorPickerSessionFile(sessionFilePath, session) {
|
|
146
|
-
return enqueueSessionFileOperation(sessionFilePath, () =>
|
|
147
|
-
writeLocatorPickerSessionFileImmediate(sessionFilePath, session),
|
|
148
|
-
)
|
|
149
|
-
}
|
|
150
|
-
export async function patchLocatorPickerSessionFile(sessionFilePath, patch) {
|
|
151
|
-
return enqueueSessionFileOperation(sessionFilePath, async () => {
|
|
152
|
-
const current = await readLocatorPickerSessionFile(sessionFilePath)
|
|
153
|
-
if (!current) {
|
|
154
|
-
return null
|
|
155
|
-
}
|
|
156
|
-
const nextPatch = typeof patch === 'function' ? patch(current) : patch
|
|
157
|
-
const nextSession = Object.assign(Object.assign(Object.assign({}, current), nextPatch), {
|
|
158
|
-
updatedAt: new Date().toISOString(),
|
|
159
|
-
})
|
|
160
|
-
return writeLocatorPickerSessionFileImmediate(sessionFilePath, nextSession)
|
|
161
|
-
})
|
|
162
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export type CompanionSessionStatus = 'starting' | 'ready' | 'picked' | 'saving' | 'closed' | 'error'
|
|
2
|
-
export type CompanionPickedLocatorStrategy = 'test-id' | 'role' | 'label' | 'placeholder' | 'id' | 'css' | 'xpath'
|
|
3
|
-
export interface CompanionLaunchSource {
|
|
4
|
-
environmentId?: string
|
|
5
|
-
environmentName?: string
|
|
6
|
-
url: string
|
|
7
|
-
}
|
|
8
|
-
export interface CompanionPickedLocatorPayload {
|
|
9
|
-
selector: string
|
|
10
|
-
currentUrl: string
|
|
11
|
-
pathname: string
|
|
12
|
-
pageTitle: string
|
|
13
|
-
tagName: string
|
|
14
|
-
text?: string
|
|
15
|
-
accessibleName?: string
|
|
16
|
-
strategy?: CompanionPickedLocatorStrategy
|
|
17
|
-
}
|
|
18
|
-
export interface CompanionSessionFile {
|
|
19
|
-
sessionId: string
|
|
20
|
-
status: CompanionSessionStatus
|
|
21
|
-
launchSource: CompanionLaunchSource
|
|
22
|
-
currentUrl: string
|
|
23
|
-
currentPathname: string
|
|
24
|
-
pageTitle: string
|
|
25
|
-
companionPid: number | null
|
|
26
|
-
crashLogPath?: string
|
|
27
|
-
pickedLocator?: CompanionPickedLocatorPayload
|
|
28
|
-
error?: string
|
|
29
|
-
startedAt: string
|
|
30
|
-
updatedAt: string
|
|
31
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|
|
Binary file
|