Skip to content

Commit b8c250a

Browse files
committed
feat: release v1.24.1 with a new CLAUDE.md template for project instructions, improved locator strategies in documentation, and enhancements to the setup script for automatic CLAUDE.md installation
1 parent 8d45787 commit b8c250a

12 files changed

Lines changed: 308 additions & 97 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## [1.24.1] - 2026-03-17
4+
5+
### Added
6+
7+
- **CLAUDE.md template:** New `templates/CLAUDE.md` with project overview, structure, conventions, and AI tooling for Playwright TypeScript projects. Setup script copies it to the project root when installing skills (unless one exists; use `--force` to overwrite).
8+
- **Setup (bin/setup.js):** When installing skills, setup now installs the CLAUDE.md template and links the Cursor project rule so Cursor and Claude Code share the same project instructions.
9+
10+
### Changed
11+
12+
- **Locators skill (references/locators.md):** Priority reordered so unique **XPath/CSS with stable attributes** come before **role/text** locators. Prefer stable selectors so failures distinguish "element missing" (bug) from "copy/locale changed." Added checkout-complete example using `[data-test="complete-header"]` and asserting text separately.
13+
- **Sauce Demo checkout page:** `orderCompleteMessage` now uses stable selector `getLocator('[data-test="complete-header"]')` instead of `getLocatorByRole('heading', { name: /thank you for your order/i })`; text is still asserted in `expectElementToContainText`.
14+
- **Package:** `overrides` added so `playwright` resolves to stable only (`^1.58.2`) when using `@playwright/cli`; `templates` included in published `files`.
15+
- **Agents:** Minor updates to playwright-test-generator, playwright-test-healer, and playwright-test-planner.
16+
- **Cursor rules / README:** Small updates for project and docs.
17+
318
## [1.24.0] - 2026-03-16
419

520
### Added

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ This library ships with AI skills and agent workflows for [Claude Code](https://
9393
| **Skills** — API docs, locator strategy, function references | `.claude/skills/vasu-playwright-utils/` | Claude Code, Cursor |
9494
| **Playwright CLI skills** — browser automation commands | `.claude/skills/playwright-cli/` | Claude Code, Cursor |
9595
| **Agents** — test planner, generator, healer workflows | `.claude/agents/` | Claude Code |
96+
| **CLAUDE.md** — project instructions template | `CLAUDE.md` (project root) | Claude Code, Cursor |
9697
| **Cursor rules** — agent workflow rules with `@file` refs | `.cursor/rules/` | Cursor |
97-
| **CLAUDE.md loader** — links your `CLAUDE.md` into Cursor | `.cursor/rules/project.mdc` | Cursor |
98+
| **CLAUDE.md loader** — links `CLAUDE.md` into Cursor | `.cursor/rules/project.mdc` | Cursor |
9899

99-
Files are **copied** (not symlinked) into your project. Both Claude Code and Cursor auto-discover `.claude/skills/`. Claude Code also auto-discovers `.claude/agents/`. Cursor uses `.cursor/rules/` to reference agent files. If your project has a `CLAUDE.md`, the setup also creates a Cursor rule that loads it via `@file CLAUDE.md`, so both tools share the same project instructions.
100+
Files are **copied** (not symlinked) into your project. Both Claude Code and Cursor auto-discover `.claude/skills/`. Claude Code also auto-discovers `.claude/agents/`. Cursor uses `.cursor/rules/` to reference agent files. The setup copies a `CLAUDE.md` template to your project root (skipped if one already exists) and creates a Cursor rule that loads it via `@file CLAUDE.md`, so both tools share the same project instructions.
100101

101102
### For consumers
102103

agents/playwright-test-generator.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,11 @@ For each test you generate:
9292

9393
## Required Test Structure
9494

95+
**Preferred: Use project's page-setup** (automatically calls `setPage(page)` before each test):
96+
9597
```typescript
96-
import { test } from '@playwright/test';
98+
import { test } from '@pagesetup';
9799
import {
98-
setPage,
99100
gotoURL,
100101
click,
101102
clickAndNavigate,
@@ -123,8 +124,7 @@ import {
123124
} from 'vasu-playwright-utils';
124125

125126
test.describe('Test Suite Name', () => {
126-
test('Test Case Name', async ({ page }) => {
127-
setPage(page);
127+
test('Test Case Name', async () => {
128128
// 1. Navigate to the application
129129
await gotoURL('https://example.com');
130130

@@ -141,6 +141,8 @@ test.describe('Test Suite Name', () => {
141141
});
142142
```
143143

144+
**Fallback (standalone, when @pagesetup is not available):** Import `test` from `@playwright/test`, destructure `{ page }`, and call `setPage(page)` manually.
145+
144146
<example-generation>
145147
For following plan:
146148

@@ -166,12 +168,11 @@ Following file is generated:
166168
// spec: specs/plan.md
167169
// seed: tests/seed.spec.ts
168170

169-
import { test } from '@playwright/test';
170-
import { setPage, gotoURL, fill, fillAndEnter, expectElementToBeVisible, getLocatorByPlaceholder } from 'vasu-playwright-utils';
171+
import { test } from '@pagesetup';
172+
import { gotoURL, fill, fillAndEnter, expectElementToBeVisible, getLocatorByPlaceholder } from 'vasu-playwright-utils';
171173

172174
test.describe('Adding New Todos', () => {
173-
test('Add Valid Todo', async ({ page }) => {
174-
setPage(page);
175+
test('Add Valid Todo', async () => {
175176
// 1. Click in the "What needs to be done?" input field
176177
await fill(getLocatorByPlaceholder('What needs to be done?'), 'Buy groceries');
177178

agents/playwright-test-healer.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ Use standard Playwright CLI for running tests:
125125
- Document your findings and reasoning for each fix
126126
- Prefer robust, maintainable solutions over quick hacks
127127
- Use `vasu-playwright-utils` functions for all test code
128-
- Ensure `setPage(page)` is called in test setup before any utility functions
128+
- Ensure tests import `test` from `@pagesetup` or `@fixturesetup` (which call `setPage(page)` automatically). If using `@playwright/test` directly, `setPage(page)` must be called manually.
129129
- If multiple errors exist, fix them one at a time and retest
130130
- Provide clear explanations of what was broken and how you fixed it
131131
- Continue until the test runs successfully without any failures or errors

agents/playwright-test-planner.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ You will:
8282
- Include negative testing scenarios
8383
- Ensure scenarios are independent and can be run in any order
8484
- Reference `vasu-playwright-utils` functions in step descriptions so tests can be directly implemented
85+
- Follow the locator strategy priority in `references/locators.md` when noting selectors (prefer data-testid, role, label over CSS/XPath)
8586

8687
**Output Format**: Save the complete test plan as a markdown file with clear headings, numbered steps, and
8788
professional formatting suitable for sharing with development and QA teams.

bin/setup.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,27 @@ if (INSTALL_SKILLS) {
124124
path.join(cursorRulesDir, 'vasu-playwright-utils.mdc'),
125125
path.join(projectRoot, '.cursor', 'rules', 'vasu-playwright-utils.mdc'),
126126
);
127+
128+
// Copy CLAUDE.md template if consumer doesn't have one (or --force)
129+
const claudeMdSrc = path.join(pkgDir, 'templates', 'CLAUDE.md');
130+
if (fs.existsSync(claudeMdSrc)) {
131+
const claudeMdDest = path.join(projectRoot, 'CLAUDE.md');
132+
console.log(`\n${step++}. Installing CLAUDE.md template:`);
133+
const rel = path.relative(projectRoot, claudeMdDest);
134+
if (!FORCE && fs.existsSync(claudeMdDest)) {
135+
console.log(` [skip] ${rel} (exists, use --force to overwrite)`);
136+
} else {
137+
fs.copyFileSync(claudeMdSrc, claudeMdDest);
138+
console.log(` [copy] ${rel}`);
139+
}
140+
}
141+
142+
// Install CLAUDE.md loader for Cursor (so Cursor reads the same project instructions as Claude Code)
143+
console.log(`\n${step++}. Linking CLAUDE.md for Cursor:`);
144+
installCursorRule(
145+
path.join(cursorRulesDir, 'project.mdc'),
146+
path.join(projectRoot, '.cursor', 'rules', 'project.mdc'),
147+
);
127148
}
128149

129150
if (INSTALL_AGENTS) {
@@ -141,15 +162,6 @@ if (INSTALL_AGENTS) {
141162
);
142163
}
143164

144-
// Install CLAUDE.md loader for Cursor (so Cursor reads the same project instructions as Claude Code)
145-
if (fs.existsSync(path.join(projectRoot, 'CLAUDE.md'))) {
146-
console.log(`\n${step++}. Linking CLAUDE.md for Cursor:`);
147-
installCursorRule(
148-
path.join(cursorRulesDir, 'project.mdc'),
149-
path.join(projectRoot, '.cursor', 'rules', 'project.mdc'),
150-
);
151-
}
152-
153165
// Summary
154166
console.log('\nDone! Installed to:');
155167
for (const p of installed) {

cursor-rules/project.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: Project-wide instructions and conventions for vasu-playwright-utils
2+
description: Project-wide instructions and conventions
33
globs: ["**/*"]
44
---
55

package-lock.json

Lines changed: 9 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vasu-playwright-utils",
3-
"version": "1.24.0",
3+
"version": "1.24.1",
44
"description": "Playwright Typescript Library with reusable utilities",
55
"main": "./dist/src/vasu-playwright-lib/index.js",
66
"types": "./dist/src/vasu-playwright-lib/index.d.ts",
@@ -49,7 +49,8 @@
4949
"skills",
5050
"agents",
5151
"cursor-rules",
52-
"bin"
52+
"bin",
53+
"templates"
5354
],
5455
"engines": {
5556
"node": ">=20.0.0"
@@ -80,6 +81,9 @@
8081
"peerDependencies": {
8182
"@playwright/test": ">=1.58.2"
8283
},
84+
"overrides": {
85+
"playwright": "^1.58.2"
86+
},
8387
"scripts": {
8488
"ncu:check": "npx npm-check-updates --reject \"eslint,@eslint/js\" --dep prod,dev,optional,peer && npx npm-check-updates \"eslint,@eslint/js\" --target minor --deep peer",
8589
"ncu:update": "npx npm-check-updates --reject \"eslint,@eslint/js\" --dep prod,dev,optional,peer -u && npx npm-check-updates \"eslint,@eslint/js\" --target minor --deep peer -u",

skills/vasu-playwright-utils/references/locators.md

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Source: `src/vasu-playwright-lib/utils/locator-utils.ts`
44

55
## Locator Strategy Priority
66

7-
When choosing locators for test code, follow this priority order (best to worst). Always prefer the highest-priority strategy that uniquely identifies the element.
7+
When choosing locators for test code, follow this priority order (best to worst). **Prefer unique CSS or XPath with stable attributes over text-based locators** so that when a check fails you can tell quickly whether the element is missing (bug) or the copy changed (new functionality / locale).
88

99
### 1. `data-testid` attributes (Best)
1010

@@ -23,8 +23,11 @@ Custom data attributes that carry stable, semantic meaning.
2323
// HTML: <div data-product-id="shoes-001">...</div>
2424
await click('[data-product-id="shoes-001"]');
2525

26-
// HTML: <tr data-row-type="header">...</tr>
27-
await expectElementToBeVisible('[data-row-type="header"]');
26+
// HTML: <h2 data-test="complete-header">Thank you for your order</h2>
27+
const orderCompleteMessage = () => getLocator('[data-test="complete-header"]');
28+
await expectElementToContainText(orderCompleteMessage(), /thank you for your order/i, {
29+
message: 'Checkout complete message should be displayed',
30+
});
2831
```
2932

3033
### 3. `id` attributes
@@ -51,34 +54,9 @@ await fill('[name="email"]', 'user@example.com');
5154
await selectByText('[name="country"]', 'United States');
5255
```
5356

54-
### 5. Playwright built-in locators
57+
### 5. XPath with unique attributes
5558

56-
Semantic locators that match how users perceive the page. Resilient to DOM changes.
57-
58-
```typescript
59-
// By ARIA role + accessible name (preferred for interactive elements)
60-
await click(getLocatorByRole('button', { name: 'Submit' }));
61-
await fill(getLocatorByRole('textbox', { name: 'Email' }), 'user@example.com');
62-
await click(getLocatorByRole('link', { name: 'Sign up' }));
63-
await check(getLocatorByRole('checkbox', { name: 'Remember me' }));
64-
65-
// By label text (preferred for form fields)
66-
await fill(getLocatorByLabel('Email address'), 'user@example.com');
67-
68-
// By placeholder text
69-
await fill(getLocatorByPlaceholder('Search...'), 'playwright');
70-
71-
// By visible text content
72-
await click(getLocatorByText('Add to cart'));
73-
74-
// Use regex for partial or case-insensitive matching
75-
await click(getLocatorByRole('button', { name: /submit/i }));
76-
await click(getLocatorByText(/view details/i));
77-
```
78-
79-
### 6. XPath with unique attributes
80-
81-
Use only when higher-priority strategies are unavailable. Target stable attributes.
59+
Use when no data-_ or id is available. Target **stable attributes** (e.g. `data-test`, `aria-_`, `type`), not text.
8260

8361
```typescript
8462
// Good: XPath with stable attributes
@@ -89,9 +67,9 @@ await click('//input[@type="email"]');
8967
await click('//div[@data-section="billing"]//button[@type="submit"]');
9068
```
9169

92-
### 7. CSS with unique attributes
70+
### 6. CSS with unique attributes
9371

94-
Similar to XPath — use stable attributes, not structural position.
72+
Use stable attribute selectors so the locator does not depend on copy or locale.
9573

9674
```typescript
9775
// Good: attribute-based CSS
@@ -100,6 +78,42 @@ await fill('input[type="email"]', 'user@example.com');
10078

10179
// Good: scoped by stable parent
10280
await click('.billing-section button[type="submit"]');
81+
82+
// Good: data-test (e.g. Sauce Demo checkout complete)
83+
const orderCompleteMessage = () => getLocator('[data-test="complete-header"]');
84+
await expectElementToContainText(orderCompleteMessage(), /thank you for your order/i);
85+
```
86+
87+
### 7. Playwright built-in locators (role / text) — use only when no stable selector exists
88+
89+
Text- and role-based locators are **flaky**: they change with copy, locale, and country. If the only way to find an element is by its text, a failure does not tell you whether the element is missing (bug) or the wording changed (new feature / i18n). Prefer **data-testid**, **data-\***, **id**, or **unique CSS/XPath** first.
90+
91+
When you must use role or text:
92+
93+
```typescript
94+
// By ARIA role + accessible name
95+
await click(getLocatorByRole('button', { name: 'Submit' }));
96+
await fill(getLocatorByRole('textbox', { name: 'Email' }), 'user@example.com');
97+
98+
// By label text (form fields)
99+
await fill(getLocatorByLabel('Email address'), 'user@example.com');
100+
101+
// By placeholder text
102+
await fill(getLocatorByPlaceholder('Search...'), 'playwright');
103+
104+
// By visible text — avoid for assertions; use stable selector + assert text separately
105+
await click(getLocatorByText('Add to cart'));
106+
```
107+
108+
**Assertions:** Prefer a **stable locator** for the element and assert the **text in the assertion**. That way a failure shows "expected text X, got Y" (copy change) vs element not found (bug).
109+
110+
```typescript
111+
// Prefer: stable selector + text in assertion
112+
const orderCompleteMessage = () => getLocator('[data-test="complete-header"]');
113+
await expectElementToContainText(orderCompleteMessage(), /thank you for your order/i);
114+
115+
// Avoid: locating by text — fails ambiguously if copy or locale changes
116+
// const orderCompleteMessage = () => getLocatorByRole('heading', { name: /thank you for your order/i });
103117
```
104118

105119
### 8. XPath (structural)

0 commit comments

Comments
 (0)