@sun-asterisk/sungen 2.6.10 → 2.6.12

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.
Files changed (35) hide show
  1. package/dist/cli/index.js +1 -1
  2. package/dist/dashboard/templates/index.html +112 -101
  3. package/dist/exporters/xlsx-exporter.d.ts +6 -0
  4. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  5. package/dist/exporters/xlsx-exporter.js +53 -19
  6. package/dist/exporters/xlsx-exporter.js.map +1 -1
  7. package/dist/generators/test-generator/utils/selector-resolver.d.ts +9 -0
  8. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  9. package/dist/generators/test-generator/utils/selector-resolver.js +18 -2
  10. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  11. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  12. package/dist/orchestrator/project-initializer.js +5 -5
  13. package/dist/orchestrator/project-initializer.js.map +1 -1
  14. package/dist/orchestrator/templates/ai-instructions/claude-config.md +2 -0
  15. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +1 -0
  16. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +38 -0
  17. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +2 -0
  18. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +2 -0
  19. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -0
  20. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +38 -0
  21. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +2 -0
  22. package/package.json +1 -1
  23. package/src/cli/index.ts +1 -1
  24. package/src/dashboard/templates/index.html +112 -101
  25. package/src/exporters/xlsx-exporter.ts +58 -19
  26. package/src/generators/test-generator/utils/selector-resolver.ts +19 -2
  27. package/src/orchestrator/project-initializer.ts +5 -5
  28. package/src/orchestrator/templates/ai-instructions/claude-config.md +2 -0
  29. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +1 -0
  30. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-keys.md +38 -0
  31. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +2 -0
  32. package/src/orchestrator/templates/ai-instructions/copilot-config.md +2 -0
  33. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +1 -0
  34. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +38 -0
  35. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +2 -0
@@ -12,6 +12,7 @@
12
12
  import * as fs from 'fs';
13
13
  import * as path from 'path';
14
14
  import ExcelJS from 'exceljs';
15
+ import JSZip from 'jszip';
15
16
  import { ScreenSummary, TestCaseRow } from './types';
16
17
  import { getPackageVersion } from './package-info';
17
18
  import { SUN_LOGO_PNG_BASE64 } from './sun-logo';
@@ -54,7 +55,7 @@ export function renderXlsx(
54
55
 
55
56
  // -- Column widths matching template_report.xlsx (Sample sheet) --
56
57
  ws.columns = [
57
- { width: 6.38 }, // A — TC ID
58
+ { width: 20 }, // A — TC ID (wider to fit long flow IDs like FLOW-KUDO-…)
58
59
  { width: 12.5 }, // B — Screen/Function
59
60
  { width: 15.38 }, // C — Big item
60
61
  { width: 16.25 }, // D — Medium item
@@ -92,28 +93,24 @@ export function renderXlsx(
92
93
  ws.mergeCells('A4:F4');
93
94
  ws.mergeCells('G4:H4');
94
95
 
95
- // A1 (logo band, merged A1:C3) — give it bordering + embed the Sun* logo.
96
+ // A1 (logo band, merged A1:C3) — border + embedded Sun* logo (base64 inline).
96
97
  const a1 = ws.getCell('A1');
97
98
  a1.alignment = { horizontal: 'center', vertical: 'middle' };
98
99
  a1.border = allBordersBlack;
99
100
 
100
- // Embed Sun* logo using a oneCellAnchor + explicit pixel `ext`, exactly
101
- // like the template. tl is offset into the merged band so the logo lands
102
- // centred in A1:C3 instead of pinned to the top-left corner.
103
101
  try {
104
102
  const imageId = wb.addImage({
105
103
  buffer: Buffer.from(SUN_LOGO_PNG_BASE64, 'base64') as unknown as ExcelJS.Buffer,
106
104
  extension: 'png',
107
105
  });
108
- // A1:C3 240 × 60 px. For a 90×50 logo centred horizontally / vertically:
109
- // left margin = (240 90)/2 ≈ 75 px 9525 EMU/px 714375 EMU
110
- // top margin = (60 − 50)/2 5 px 47625 EMU
111
- // ExcelJS's fractional `col`/`row` clamps to a small EMU range, so we
112
- // bypass it and write `native*` fields directly (the underlying XML uses
113
- // the same field names).
106
+ // Centre a fixed-size 90×51 logo inside A1:C3 using absolute EMU offsets.
107
+ // Col widths (A=20, B=12.5, C=15.38) → pixels via Excel's `w*7+5` rule:
108
+ // A=145, B=92.5, C=112.66 ⇒ A1:C3350 px wide.
109
+ // Left padding for a 90-px image = (350−90)/2 ≈ 130 px = 1,237,500 EMU.
110
+ // 3 default rows × 20 px = 60 px; top padding ≈ 4.5 px ≈ 42,862 EMU.
114
111
  ws.addImage(imageId, {
115
- tl: { nativeCol: 0, nativeColOff: 714375, nativeRow: 0, nativeRowOff: 47625 } as unknown as ExcelJS.Anchor,
116
- ext: { width: 90, height: 50 },
112
+ tl: { nativeCol: 0, nativeColOff: 1237500, nativeRow: 0, nativeRowOff: 42862 } as unknown as ExcelJS.Anchor,
113
+ ext: { width: 90, height: 51 },
117
114
  editAs: 'oneCell',
118
115
  } as unknown as ExcelJS.ImageRange);
119
116
  } catch { /* logo is decorative — never block export */ }
@@ -161,8 +158,7 @@ export function renderXlsx(
161
158
  g4.alignment = { horizontal: 'right', vertical: 'middle', wrapText: true };
162
159
  g4.border = allBordersBlack;
163
160
 
164
- // -- Row 5: spacer (height 12) --
165
- ws.getRow(5).height = 12;
161
+ // -- Row 5: spacer (height auto-fit). --
166
162
 
167
163
  // -- Row 6: Summary headers (cols C..H), lavender fill --
168
164
  const sumLabels: Record<string, string> = {
@@ -176,7 +172,7 @@ export function renderXlsx(
176
172
  c.alignment = { horizontal: 'center', vertical: 'top' };
177
173
  c.border = allBordersBlack;
178
174
  }
179
- ws.getRow(6).height = 12;
175
+ // No explicit height Excel auto-fits this row's content.
180
176
 
181
177
  // -- Row 7: Counts (cols C..H), live formulas referencing the data area --
182
178
  // Data table starts at row 15 (col header at row 14, divider at row 14? actually
@@ -200,7 +196,7 @@ export function renderXlsx(
200
196
  c.border = allBordersBlack;
201
197
  if (col === 'H') c.numFmt = '#,##0';
202
198
  }
203
- ws.getRow(7).height = 12;
199
+ // No explicit height Excel auto-fits.
204
200
 
205
201
  // C8 has no value in the template but still needs a border so the band
206
202
  // visually closes underneath "Total TCs".
@@ -227,7 +223,7 @@ export function renderXlsx(
227
223
  c.border = allBordersBlack;
228
224
  c.numFmt = '0%';
229
225
  }
230
- ws.getRow(8).height = 12;
226
+ // No explicit height Excel auto-fits.
231
227
 
232
228
  // After rows 1-8 are populated, append a blank row 9 spacer.
233
229
  while (ws.rowCount < 9) ws.addRow([]);
@@ -353,6 +349,17 @@ export function renderXlsx(
353
349
  to: { row: ws.rowCount, column: COL_COUNT },
354
350
  };
355
351
 
352
+ // Sheet protection lets the picLocks on the embedded logo take effect
353
+ // (Excel honours image locks only when the sheet is protected). Empty
354
+ // password — QA can run Review → Unprotect Sheet if they need to edit.
355
+ // `objects: false` here is ExcelJS's inverted semantic that writes
356
+ // `objects="1"` in XML (= objects ARE locked).
357
+ (ws as unknown as { sheetProtection: object }).sheetProtection = {
358
+ sheet: true,
359
+ objects: false,
360
+ scenarios: false,
361
+ };
362
+
356
363
  return wb;
357
364
  }
358
365
 
@@ -368,9 +375,41 @@ export async function writeXlsx(
368
375
  const outDir = path.join(cwd, 'qa', 'deliverables');
369
376
  if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
370
377
  const outPath = path.join(outDir, `${screen}-testcases.xlsx`);
371
- await wb.xlsx.writeFile(outPath);
378
+ const buffer = await wb.xlsx.writeBuffer();
379
+ const locked = await lockEmbeddedImages(Buffer.from(buffer as ArrayBuffer));
380
+ fs.writeFileSync(outPath, locked);
372
381
  return outPath;
373
382
  }
374
383
 
384
+ /**
385
+ * Post-process the workbook buffer: extend `<a:picLocks>` on every drawing
386
+ * with `noMove`, `noResize`, `noSelect`. Combined with the sheet protection
387
+ * above, the embedded logo can't be dragged, resized, or selected via the UI.
388
+ */
389
+ export async function lockEmbeddedImages(buffer: Buffer): Promise<Buffer> {
390
+ const zip = await JSZip.loadAsync(buffer);
391
+ const drawingFiles = Object.keys(zip.files).filter(
392
+ (name) => /^xl\/drawings\/drawing\d+\.xml$/.test(name)
393
+ );
394
+ for (const name of drawingFiles) {
395
+ const file = zip.file(name);
396
+ if (!file) continue;
397
+ const xml = await file.async('string');
398
+ const patched = xml.replace(
399
+ /<a:picLocks([^/]*)\/>/g,
400
+ (_match, attrs: string) => {
401
+ const has = (k: string) => new RegExp(`\\b${k}=`).test(attrs);
402
+ const additions = ['noMove', 'noResize', 'noSelect']
403
+ .filter((k) => !has(k))
404
+ .map((k) => ` ${k}="1"`)
405
+ .join('');
406
+ return `<a:picLocks${attrs}${additions}/>`;
407
+ }
408
+ );
409
+ zip.file(name, patched);
410
+ }
411
+ return zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' });
412
+ }
413
+
375
414
  void applyBorder;
376
415
  void ({} as AnyRow);
@@ -4,6 +4,7 @@ import yaml from 'yaml';
4
4
  import { readYaml, readYamlIfExists } from '../../../utils/yaml-io';
5
5
 
6
6
  import { SelectorType } from '../../../utils/selector-types';
7
+ import { DataResolver } from './data-resolver';
7
8
 
8
9
  // Structured selector format v2
9
10
  interface SelectorEntry {
@@ -414,6 +415,20 @@ export class SelectorResolver {
414
415
  return this.resolveNaturalLanguage(selectorRef, this.featureName);
415
416
  }
416
417
 
418
+ /**
419
+ * Replace {{variable}} references in selector values with runtime data markers.
420
+ * This enables i18n: selectors can reference locale-dependent text from test-data.
421
+ *
422
+ * "{{lbl_submit}}" → "__SUNGEN_TD_lbl_submit__"
423
+ * "Hello {{name}}" → "Hello __SUNGEN_TD_name__"
424
+ * "No variables" → "No variables" (unchanged)
425
+ */
426
+ private static resolveTemplateVars(value: string): string {
427
+ return value.replace(/\{\{([^}]+)\}\}/g, (_, varName) => {
428
+ return DataResolver.encodeMarker(varName.trim());
429
+ });
430
+ }
431
+
417
432
  /**
418
433
  * Resolve from new structured entry format
419
434
  */
@@ -422,9 +437,11 @@ export class SelectorResolver {
422
437
  const locator = entry.locator || '';
423
438
  const type = entry.type || 'placeholder';
424
439
  // Check if value exists in entry (even if empty string), use it; otherwise use originalLabel
425
- const value = entry.value !== undefined && entry.value !== null ? entry.value : originalLabel;
440
+ const rawValue = entry.value !== undefined && entry.value !== null ? entry.value : originalLabel;
426
441
  // Check if name exists in entry (even if empty string), use it; otherwise use originalLabel
427
- const name = entry.name !== undefined && entry.name !== null ? entry.name : originalLabel;
442
+ const rawName = entry.name !== undefined && entry.name !== null ? entry.name : originalLabel;
443
+ const value = SelectorResolver.resolveTemplateVars(rawValue);
444
+ const name = SelectorResolver.resolveTemplateVars(rawName);
428
445
  const nth = entry.nth !== undefined && entry.nth !== null ? entry.nth : undefined;
429
446
  const exact = entry.exact === true || entry.match === 'exact';
430
447
  const inputMethod = entry.inputMethod;
@@ -147,7 +147,6 @@ export class ProjectInitializer {
147
147
  const gitignorePath = path.join(this.cwd, '.gitignore');
148
148
  const sungenEntries = [
149
149
  '# Sungen generated files',
150
- 'specs/generated/',
151
150
  'specs/.auth/',
152
151
  'test-results/',
153
152
  'playwright-report/',
@@ -162,7 +161,7 @@ export class ProjectInitializer {
162
161
  } else {
163
162
  // Append Sungen entries if not already present
164
163
  let content = fs.readFileSync(gitignorePath, 'utf-8');
165
- const needsUpdate = !content.includes('specs/generated/');
164
+ const needsUpdate = !content.includes('specs/.auth/');
166
165
 
167
166
  if (needsUpdate) {
168
167
  content += '\n' + sungenEntries.join('\n') + '\n';
@@ -450,8 +449,9 @@ export class ProjectInitializer {
450
449
  console.log(`📦 Installing ${missingDeps.join(', ')}...\n`);
451
450
  execSync(`npm install -D ${missingDeps.join(' ')}`, execOpts);
452
451
 
453
- console.log('\n🎭 Installing Playwright browsers...\n');
454
- execSync('npx playwright install', execOpts);
452
+ console.log('\n🎭 Installing Chromium (default browser)...\n');
453
+ execSync('npx playwright install --with-deps chromium', execOpts);
454
+ console.log('\n💡 To install other browsers: npm run install:browsers -- firefox webkit\n');
455
455
  }
456
456
 
457
457
  /**
@@ -477,7 +477,7 @@ export class ProjectInitializer {
477
477
  'test:ui': 'playwright test specs/generated/ --ui',
478
478
  'report': 'playwright show-report',
479
479
  'generate': 'sungen generate --all',
480
- 'install:browsers': 'npx playwright install chromium',
480
+ 'install:browsers': 'npx playwright install',
481
481
  };
482
482
 
483
483
  let added = 0;
@@ -82,6 +82,8 @@ qa/deliverables/<name>-testcases.xlsx # Styled workbook for client hand-off
82
82
 
83
83
  **Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
84
84
 
85
+ **i18n support**: for multilingual sites, use `{{variable}}` in selector `name`/`value` fields to reference locale-dependent text from test-data. Create locale overlay files (e.g., `<screen>.vi.yaml`, `<screen>.staging-ja.yaml`) and run with `SUNGEN_ENV=vi`. One feature file + one selector file works across all locales. See `sungen-selector-keys` skill for details.
86
+
85
87
  ## CLI Commands
86
88
 
87
89
  ```bash
@@ -61,6 +61,7 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
61
61
  - Selector priority: follow the table in **Diagnosis & Fix § Step 3** (`testid` > `role`+name > `placeholder` > `label` > `locator` > `text`).
62
62
  - Copy names **character-for-character** from the snapshot. Never infer from the Gherkin label.
63
63
  - If an element is auto-inferable per `sungen-selector-keys` § Auto-Infer, **omit it** from YAML — keep the file minimal.
64
+ - **i18n sites**: if the site supports multiple languages, use `{{variable}}` in `name`/`value` fields instead of hardcoded text. Add corresponding `lbl_*` keys to `test-data.yaml` + locale overlay files (see `sungen-selector-keys` § i18n).
64
65
  7. **Substring ambiguity check**: for each `role` + `name` selector, check if any other element in the snapshot has a name that **contains** this name as a substring (e.g., `"Đăng ký"` vs `"Đăng ký bằng Google"`). If yes → add `exact: true` to prevent strict mode violation at runtime.
65
66
  8. **Merge, don't overwrite**: preserve the page selector and any user-authored entries in `selectors.yaml`. Only add missing keys.
66
67
  9. **Show summary + confirm**: list the keys that will be added, ask the user to approve, then write the file.
@@ -115,6 +115,44 @@ gửi lời cảm ơn--3:
115
115
  name: 'Gửi lời cảm ơn'
116
116
  ```
117
117
 
118
+ ## i18n: Template Variables in Selectors
119
+
120
+ For multilingual sites without `data-testid`, use `{{variable}}` in `name` or `value` fields to reference locale-dependent text from `test-data.yaml`.
121
+
122
+ ```yaml
123
+ # selectors — one file for all locales
124
+ submit:
125
+ type: role
126
+ value: button
127
+ name: "{{lbl_submit}}"
128
+
129
+ search:
130
+ type: placeholder
131
+ value: "{{lbl_search}}"
132
+
133
+ logo:
134
+ type: testid
135
+ value: app-logo # testid is locale-independent — no variable needed
136
+ ```
137
+
138
+ ```yaml
139
+ # test-data/login.yaml (base — English)
140
+ lbl_submit: "Sign in"
141
+ lbl_search: "Search..."
142
+
143
+ # test-data/login.vi.yaml (Vietnamese)
144
+ lbl_submit: "Đăng nhập"
145
+ lbl_search: "Tìm kiếm..."
146
+ ```
147
+
148
+ Run: `SUNGEN_ENV=vi npx playwright test`
149
+
150
+ **Rules:**
151
+ 1. Prefix i18n keys with `lbl_`, `msg_`, `txt_` to separate from test data
152
+ 2. Prefer `data-testid` — only use `{{variable}}` when no stable selector exists
153
+ 3. Feature file stays identical across locales
154
+ 4. Requires runtime data mode (default, not `--inline-data`)
155
+
118
156
  ## Lookup Priority
119
157
 
120
158
  Resolver searches in this order:
@@ -352,6 +352,8 @@ Feature: <Screen> Screen
352
352
 
353
353
  **Environment-specific data**: create `<screen>.<env>.yaml` alongside the base file with only the keys that change. Users run `SUNGEN_ENV=staging npx playwright test` to merge overrides.
354
354
 
355
+ **i18n / multilingual**: use the same `SUNGEN_ENV` overlay for locale variants — e.g., `login.vi.yaml`, `login.staging-ja.yaml`. Include `lbl_*` / `msg_*` keys for selector `{{variable}}` references (see `sungen-selector-keys` § i18n). One feature file + one selector file works across all locales.
356
+
355
357
  ## Flow Test Generation
356
358
 
357
359
  When generating tests for a **flow** (`qa/flows/<name>/`), adapt the strategy:
@@ -82,6 +82,8 @@ qa/deliverables/<name>-testcases.xlsx # Styled workbook for client hand-off
82
82
 
83
83
  **Environment overrides**: `SUNGEN_ENV=staging npx playwright test` merges `<screen>.staging.yaml` on top of `<screen>.yaml`. Create `<screen>.<env>.yaml` for environment-specific values (different credentials, URLs, test users).
84
84
 
85
+ **i18n support**: for multilingual sites, use `{{variable}}` in selector `name`/`value` fields to reference locale-dependent text from test-data. Create locale overlay files (e.g., `<screen>.vi.yaml`, `<screen>.staging-ja.yaml`) and run with `SUNGEN_ENV=vi`. One feature file + one selector file works across all locales. See `sungen-selector-keys` skill for details.
86
+
85
87
  ## CLI Commands
86
88
 
87
89
  ```bash
@@ -61,6 +61,7 @@ When running Phase 0 for a **flow** (`qa/flows/<name>/`), check existing screen
61
61
  - Selector priority: follow the table in **Diagnosis & Fix § Step 3** (`testid` > `role`+name > `placeholder` > `label` > `locator` > `text`).
62
62
  - Copy names **character-for-character** from the snapshot. Never infer from the Gherkin label.
63
63
  - If an element is auto-inferable per `sungen-selector-keys` § Auto-Infer, **omit it** from YAML — keep the file minimal.
64
+ - **i18n sites**: if the site supports multiple languages, use `{{variable}}` in `name`/`value` fields instead of hardcoded text. Add corresponding `lbl_*` keys to `test-data.yaml` + locale overlay files (see `sungen-selector-keys` § i18n).
64
65
  7. **Substring ambiguity check**: for each `role` + `name` selector, check if any other element in the snapshot has a name that **contains** this name as a substring (e.g., `"Đăng ký"` vs `"Đăng ký bằng Google"`). If yes → add `exact: true` to prevent strict mode violation at runtime.
65
66
  8. **Merge, don't overwrite**: preserve the page selector and any user-authored entries in `selectors.yaml`. Only add missing keys.
66
67
  9. **Show summary + confirm**: list the keys that will be added, ask the user to approve, then write the file.
@@ -115,6 +115,44 @@ gửi lời cảm ơn--3:
115
115
  name: 'Gửi lời cảm ơn'
116
116
  ```
117
117
 
118
+ ## i18n: Template Variables in Selectors
119
+
120
+ For multilingual sites without `data-testid`, use `{{variable}}` in `name` or `value` fields to reference locale-dependent text from `test-data.yaml`.
121
+
122
+ ```yaml
123
+ # selectors — one file for all locales
124
+ submit:
125
+ type: role
126
+ value: button
127
+ name: "{{lbl_submit}}"
128
+
129
+ search:
130
+ type: placeholder
131
+ value: "{{lbl_search}}"
132
+
133
+ logo:
134
+ type: testid
135
+ value: app-logo # testid is locale-independent — no variable needed
136
+ ```
137
+
138
+ ```yaml
139
+ # test-data/login.yaml (base — English)
140
+ lbl_submit: "Sign in"
141
+ lbl_search: "Search..."
142
+
143
+ # test-data/login.vi.yaml (Vietnamese)
144
+ lbl_submit: "Đăng nhập"
145
+ lbl_search: "Tìm kiếm..."
146
+ ```
147
+
148
+ Run: `SUNGEN_ENV=vi npx playwright test`
149
+
150
+ **Rules:**
151
+ 1. Prefix i18n keys with `lbl_`, `msg_`, `txt_` to separate from test data
152
+ 2. Prefer `data-testid` — only use `{{variable}}` when no stable selector exists
153
+ 3. Feature file stays identical across locales
154
+ 4. Requires runtime data mode (default, not `--inline-data`)
155
+
118
156
  ## Lookup Priority
119
157
 
120
158
  Resolver searches in this order:
@@ -352,6 +352,8 @@ Feature: <Screen> Screen
352
352
 
353
353
  **Environment-specific data**: create `<screen>.<env>.yaml` alongside the base file with only the keys that change. Users run `SUNGEN_ENV=staging npx playwright test` to merge overrides.
354
354
 
355
+ **i18n / multilingual**: use the same `SUNGEN_ENV` overlay for locale variants — e.g., `login.vi.yaml`, `login.staging-ja.yaml`. Include `lbl_*` / `msg_*` keys for selector `{{variable}}` references (see `sungen-selector-keys` § i18n). One feature file + one selector file works across all locales.
356
+
355
357
  ## Flow Test Generation
356
358
 
357
359
  When generating tests for a **flow** (`qa/flows/<name>/`), adapt the strategy: