cypress-on-rails 1.18.0 → 1.19.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.
- checksums.yaml +4 -4
- data/.github/workflows/claude-code-review.yml +57 -0
- data/.github/workflows/claude.yml +50 -0
- data/CHANGELOG.md +319 -98
- data/README.md +93 -1
- data/RELEASING.md +200 -0
- data/Rakefile +1 -4
- data/cypress-on-rails.gemspec +1 -0
- data/docs/BEST_PRACTICES.md +678 -0
- data/docs/DX_IMPROVEMENTS.md +163 -0
- data/docs/PLAYWRIGHT_GUIDE.md +554 -0
- data/docs/RELEASE.md +124 -0
- data/docs/TROUBLESHOOTING.md +351 -0
- data/docs/VCR_GUIDE.md +499 -0
- data/lib/cypress_on_rails/configuration.rb +24 -0
- data/lib/cypress_on_rails/railtie.rb +7 -0
- data/lib/cypress_on_rails/server.rb +197 -0
- data/lib/cypress_on_rails/state_reset_middleware.rb +58 -0
- data/lib/cypress_on_rails/version.rb +1 -1
- data/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb +12 -0
- data/lib/generators/cypress_on_rails/templates/spec/cypress/e2e/rails_examples/using_factory_bot.cy.js +2 -2
- data/lib/generators/cypress_on_rails/templates/spec/cypress/e2e/rails_examples/using_scenarios.cy.js +1 -1
- data/lib/generators/cypress_on_rails/templates/spec/cypress/support/on-rails.js +1 -1
- data/lib/tasks/cypress.rake +33 -0
- data/rakelib/release.rake +80 -0
- data/rakelib/task_helpers.rb +23 -0
- data/rakelib/update_changelog.rake +63 -0
- metadata +31 -2
@@ -0,0 +1,163 @@
|
|
1
|
+
# Developer Experience Improvements
|
2
|
+
|
3
|
+
Based on analysis of user issues and feedback, here are the key improvements made to cypress-playwright-on-rails to enhance developer experience.
|
4
|
+
|
5
|
+
## 🎯 Issues Addressed
|
6
|
+
|
7
|
+
### 1. Manual Server Management (#152, #153)
|
8
|
+
**Previous Pain Point:** Users had to manually start Rails server in a separate terminal.
|
9
|
+
|
10
|
+
**Solution Implemented:**
|
11
|
+
- ✅ Added rake tasks: `cypress:open`, `cypress:run`, `playwright:open`, `playwright:run`
|
12
|
+
- ✅ Automatic server lifecycle management
|
13
|
+
- ✅ Dynamic port selection
|
14
|
+
- ✅ Server hooks for customization
|
15
|
+
|
16
|
+
### 2. Playwright Feature Parity (#169)
|
17
|
+
**Previous Pain Point:** Playwright users lacked documentation and helper functions.
|
18
|
+
|
19
|
+
**Solution Implemented:**
|
20
|
+
- ✅ Comprehensive [Playwright Guide](PLAYWRIGHT_GUIDE.md)
|
21
|
+
- ✅ Complete helper functions in examples
|
22
|
+
- ✅ Migration guide from Cypress to Playwright
|
23
|
+
- ✅ Playwright-specific rake tasks
|
24
|
+
|
25
|
+
### 3. VCR Configuration Confusion (#175, #160)
|
26
|
+
**Previous Pain Point:** VCR integration was poorly documented and error-prone.
|
27
|
+
|
28
|
+
**Solution Implemented:**
|
29
|
+
- ✅ Detailed [VCR Integration Guide](VCR_GUIDE.md)
|
30
|
+
- ✅ Troubleshooting for common VCR errors
|
31
|
+
- ✅ GraphQL-specific VCR configuration
|
32
|
+
- ✅ Examples for both insert/eject and use_cassette modes
|
33
|
+
|
34
|
+
### 4. Test Environment Issues (#157, #118)
|
35
|
+
**Previous Pain Point:** Confusion about running in test vs development environment.
|
36
|
+
|
37
|
+
**Solution Implemented:**
|
38
|
+
- ✅ Clear documentation in [Troubleshooting Guide](TROUBLESHOOTING.md)
|
39
|
+
- ✅ Environment configuration examples
|
40
|
+
- ✅ Support for `CYPRESS_RAILS_HOST` and `CYPRESS_RAILS_PORT`
|
41
|
+
- ✅ Guidance on enabling file watching in test environment
|
42
|
+
|
43
|
+
### 5. Database Management (#155, #114)
|
44
|
+
**Previous Pain Point:** Database cleaning issues and lack of transactional support.
|
45
|
+
|
46
|
+
**Solution Implemented:**
|
47
|
+
- ✅ Transactional test mode with automatic rollback
|
48
|
+
- ✅ Smart database cleaning strategies
|
49
|
+
- ✅ ApplicationRecord error handling
|
50
|
+
- ✅ Rails transactional fixtures support
|
51
|
+
|
52
|
+
### 6. Authentication & Security (#137)
|
53
|
+
**Previous Pain Point:** No built-in way to secure test endpoints.
|
54
|
+
|
55
|
+
**Solution Implemented:**
|
56
|
+
- ✅ `before_request` hook for authentication
|
57
|
+
- ✅ Security best practices documentation
|
58
|
+
- ✅ IP whitelisting examples
|
59
|
+
- ✅ Token-based authentication examples
|
60
|
+
|
61
|
+
## 📊 Impact Summary
|
62
|
+
|
63
|
+
### Before These Improvements
|
64
|
+
- 😤 Manual server management required
|
65
|
+
- 📖 Sparse documentation
|
66
|
+
- 🔍 Issues buried in GitHub
|
67
|
+
- 🐛 Common errors without solutions
|
68
|
+
- 🎭 Playwright as second-class citizen
|
69
|
+
|
70
|
+
### After These Improvements
|
71
|
+
- 🚀 One-command test execution
|
72
|
+
- 📚 Comprehensive documentation
|
73
|
+
- 🛠 Solutions for all common issues
|
74
|
+
- ✨ Feature parity for Playwright
|
75
|
+
- 🔒 Security best practices included
|
76
|
+
|
77
|
+
## 🗺 Documentation Structure
|
78
|
+
|
79
|
+
```
|
80
|
+
docs/
|
81
|
+
├── BEST_PRACTICES.md # Patterns and recommendations
|
82
|
+
├── TROUBLESHOOTING.md # Solutions to common issues
|
83
|
+
├── PLAYWRIGHT_GUIDE.md # Complete Playwright documentation
|
84
|
+
├── VCR_GUIDE.md # VCR integration details
|
85
|
+
└── DX_IMPROVEMENTS.md # This file
|
86
|
+
```
|
87
|
+
|
88
|
+
## 🚀 Quick Wins for New Users
|
89
|
+
|
90
|
+
1. **Start Testing in 30 Seconds**
|
91
|
+
```bash
|
92
|
+
gem 'cypress-on-rails'
|
93
|
+
bundle install
|
94
|
+
rails g cypress_on_rails:install
|
95
|
+
rails cypress:open # Done!
|
96
|
+
```
|
97
|
+
|
98
|
+
2. **Switch from cypress-rails**
|
99
|
+
- Drop-in replacement with same commands
|
100
|
+
- Migration guide in CHANGELOG
|
101
|
+
|
102
|
+
3. **Debug Failures Easily**
|
103
|
+
- Comprehensive troubleshooting guide
|
104
|
+
- Common errors with solutions
|
105
|
+
- Stack Overflow-style Q&A format
|
106
|
+
|
107
|
+
## 🔮 Future Improvements
|
108
|
+
|
109
|
+
Based on remaining open issues, consider implementing:
|
110
|
+
|
111
|
+
1. **Parallel Testing Support (#119)**
|
112
|
+
- Native parallel execution
|
113
|
+
- Automatic database partitioning
|
114
|
+
- CI-specific optimizations
|
115
|
+
|
116
|
+
2. **Better Error Messages**
|
117
|
+
- Contextual help in error output
|
118
|
+
- Links to relevant documentation
|
119
|
+
- Suggested fixes
|
120
|
+
|
121
|
+
3. **Interactive Setup Wizard**
|
122
|
+
- Guided installation process
|
123
|
+
- Framework detection
|
124
|
+
- Automatic configuration
|
125
|
+
|
126
|
+
4. **Performance Monitoring**
|
127
|
+
- Test execution metrics
|
128
|
+
- Slow test detection
|
129
|
+
- Optimization suggestions
|
130
|
+
|
131
|
+
## 💡 Developer Experience Principles
|
132
|
+
|
133
|
+
These improvements follow key DX principles:
|
134
|
+
|
135
|
+
1. **Zero to Testing Fast** - Minimize time to first test
|
136
|
+
2. **Pit of Success** - Make the right thing the easy thing
|
137
|
+
3. **Progressive Disclosure** - Simple things simple, complex things possible
|
138
|
+
4. **Excellent Error Messages** - Every error should suggest a solution
|
139
|
+
5. **Documentation as Code** - Keep docs next to implementation
|
140
|
+
6. **Community Driven** - Address real user pain points
|
141
|
+
|
142
|
+
## 📈 Metrics of Success
|
143
|
+
|
144
|
+
Improvements can be measured by:
|
145
|
+
- ⬇️ Reduced issue creation for solved problems
|
146
|
+
- ⬇️ Decreased time to first successful test
|
147
|
+
- ⬆️ Increased adoption rate
|
148
|
+
- ⬆️ Higher user satisfaction
|
149
|
+
- 🔄 More contributions from community
|
150
|
+
|
151
|
+
## 🤝 Contributing
|
152
|
+
|
153
|
+
To continue improving developer experience:
|
154
|
+
|
155
|
+
1. **Report Issues** with detailed reproduction steps
|
156
|
+
2. **Suggest Improvements** via GitHub discussions
|
157
|
+
3. **Share Solutions** that worked for you
|
158
|
+
4. **Contribute Examples** to documentation
|
159
|
+
5. **Help Others** in Slack/forums
|
160
|
+
|
161
|
+
## Conclusion
|
162
|
+
|
163
|
+
These documentation and feature improvements directly address the most common pain points users face. By providing comprehensive guides, troubleshooting resources, and automated solutions, we've significantly improved the developer experience for both new and existing users of cypress-playwright-on-rails.
|
@@ -0,0 +1,554 @@
|
|
1
|
+
# Complete Playwright Guide
|
2
|
+
|
3
|
+
This guide provides comprehensive documentation for using Playwright with cypress-playwright-on-rails.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
- [Installation](#installation)
|
7
|
+
- [Basic Setup](#basic-setup)
|
8
|
+
- [Commands and Helpers](#commands-and-helpers)
|
9
|
+
- [Factory Bot Integration](#factory-bot-integration)
|
10
|
+
- [Fixtures and Scenarios](#fixtures-and-scenarios)
|
11
|
+
- [Database Management](#database-management)
|
12
|
+
- [Advanced Configuration](#advanced-configuration)
|
13
|
+
- [Migration from Cypress](#migration-from-cypress)
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
### 1. Add the gem to your Gemfile
|
18
|
+
```ruby
|
19
|
+
group :test, :development do
|
20
|
+
gem 'cypress-on-rails', '~> 1.0'
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
### 2. Install with Playwright framework
|
25
|
+
```bash
|
26
|
+
bundle install
|
27
|
+
bin/rails g cypress_on_rails:install --framework playwright
|
28
|
+
|
29
|
+
# Or with custom folder
|
30
|
+
bin/rails g cypress_on_rails:install --framework playwright --install_folder=spec/e2e
|
31
|
+
```
|
32
|
+
|
33
|
+
### 3. Install Playwright
|
34
|
+
```bash
|
35
|
+
# Using yarn
|
36
|
+
yarn add -D @playwright/test
|
37
|
+
|
38
|
+
# Using npm
|
39
|
+
npm install --save-dev @playwright/test
|
40
|
+
|
41
|
+
# Install browsers
|
42
|
+
npx playwright install
|
43
|
+
```
|
44
|
+
|
45
|
+
## Basic Setup
|
46
|
+
|
47
|
+
### Directory Structure
|
48
|
+
```
|
49
|
+
e2e/
|
50
|
+
├── playwright/
|
51
|
+
│ ├── e2e/ # Test files
|
52
|
+
│ │ └── example.spec.js
|
53
|
+
│ ├── support/
|
54
|
+
│ │ └── on-rails.js # Helper functions
|
55
|
+
│ ├── e2e_helper.rb # Ruby helper
|
56
|
+
│ └── app_commands/ # Ruby commands
|
57
|
+
│ ├── clean.rb
|
58
|
+
│ ├── factory_bot.rb
|
59
|
+
│ └── scenarios/
|
60
|
+
│ └── basic.rb
|
61
|
+
└── playwright.config.js # Playwright configuration
|
62
|
+
```
|
63
|
+
|
64
|
+
### Playwright Configuration
|
65
|
+
```js
|
66
|
+
// playwright.config.js
|
67
|
+
module.exports = {
|
68
|
+
testDir: './e2e/playwright/e2e',
|
69
|
+
timeout: 30000,
|
70
|
+
use: {
|
71
|
+
baseURL: process.env.BASE_URL || 'http://localhost:5017',
|
72
|
+
trace: 'on-first-retry',
|
73
|
+
screenshot: 'only-on-failure',
|
74
|
+
video: 'retain-on-failure'
|
75
|
+
},
|
76
|
+
projects: [
|
77
|
+
{ name: 'chromium', use: { browserName: 'chromium' } },
|
78
|
+
{ name: 'firefox', use: { browserName: 'firefox' } },
|
79
|
+
{ name: 'webkit', use: { browserName: 'webkit' } }
|
80
|
+
]
|
81
|
+
};
|
82
|
+
```
|
83
|
+
|
84
|
+
## Commands and Helpers
|
85
|
+
|
86
|
+
### Complete on-rails.js Helper File
|
87
|
+
```js
|
88
|
+
// e2e/playwright/support/on-rails.js
|
89
|
+
const { request } = require('@playwright/test');
|
90
|
+
|
91
|
+
const API_PREFIX = ''; // or '/api' if configured
|
92
|
+
|
93
|
+
async function appCommands(body) {
|
94
|
+
const context = await request.newContext();
|
95
|
+
const response = await context.post(`${API_PREFIX}/__e2e__/command`, {
|
96
|
+
data: body,
|
97
|
+
headers: {
|
98
|
+
'Content-Type': 'application/json'
|
99
|
+
}
|
100
|
+
});
|
101
|
+
|
102
|
+
if (!response.ok()) {
|
103
|
+
const text = await response.text();
|
104
|
+
throw new Error(`Command failed: ${response.status()} - ${text}`);
|
105
|
+
}
|
106
|
+
|
107
|
+
return response.json();
|
108
|
+
}
|
109
|
+
|
110
|
+
async function app(name, commandOptions = {}) {
|
111
|
+
const result = await appCommands({
|
112
|
+
name,
|
113
|
+
options: commandOptions
|
114
|
+
});
|
115
|
+
return result[0];
|
116
|
+
}
|
117
|
+
|
118
|
+
async function appScenario(name, options = {}) {
|
119
|
+
return app(`scenarios/${name}`, options);
|
120
|
+
}
|
121
|
+
|
122
|
+
async function appFactories(factories) {
|
123
|
+
return app('factory_bot', factories);
|
124
|
+
}
|
125
|
+
|
126
|
+
async function appFixtures() {
|
127
|
+
return app('activerecord_fixtures');
|
128
|
+
}
|
129
|
+
|
130
|
+
async function appClean() {
|
131
|
+
return app('clean');
|
132
|
+
}
|
133
|
+
|
134
|
+
async function appEval(code) {
|
135
|
+
return app('eval', { code });
|
136
|
+
}
|
137
|
+
|
138
|
+
module.exports = {
|
139
|
+
app,
|
140
|
+
appCommands,
|
141
|
+
appScenario,
|
142
|
+
appFactories,
|
143
|
+
appFixtures,
|
144
|
+
appClean,
|
145
|
+
appEval
|
146
|
+
};
|
147
|
+
```
|
148
|
+
|
149
|
+
### Using Helpers in Tests
|
150
|
+
```js
|
151
|
+
// e2e/playwright/e2e/user.spec.js
|
152
|
+
const { test, expect } = require('@playwright/test');
|
153
|
+
const { app, appFactories, appScenario, appClean } = require('../support/on-rails');
|
154
|
+
|
155
|
+
test.describe('User Management', () => {
|
156
|
+
test.beforeEach(async () => {
|
157
|
+
await appClean();
|
158
|
+
});
|
159
|
+
|
160
|
+
test('create and view user', async ({ page }) => {
|
161
|
+
// Create user with factory bot
|
162
|
+
const users = await appFactories([
|
163
|
+
['create', 'user', { name: 'John Doe', email: 'john@example.com' }]
|
164
|
+
]);
|
165
|
+
|
166
|
+
await page.goto(`/users/${users[0].id}`);
|
167
|
+
await expect(page.locator('h1')).toContainText('John Doe');
|
168
|
+
});
|
169
|
+
|
170
|
+
test('load scenario', async ({ page }) => {
|
171
|
+
await appScenario('user_with_posts');
|
172
|
+
await page.goto('/users');
|
173
|
+
await expect(page.locator('.user-count')).toContainText('5 users');
|
174
|
+
});
|
175
|
+
});
|
176
|
+
```
|
177
|
+
|
178
|
+
## Factory Bot Integration
|
179
|
+
|
180
|
+
### Creating Records
|
181
|
+
```js
|
182
|
+
test('factory bot examples', async ({ page }) => {
|
183
|
+
// Single record
|
184
|
+
const user = await appFactories([
|
185
|
+
['create', 'user', { name: 'Alice' }]
|
186
|
+
]);
|
187
|
+
|
188
|
+
// Multiple records
|
189
|
+
const posts = await appFactories([
|
190
|
+
['create_list', 'post', 3, { published: true }]
|
191
|
+
]);
|
192
|
+
|
193
|
+
// With traits
|
194
|
+
const adminUser = await appFactories([
|
195
|
+
['create', 'user', 'admin', { name: 'Admin User' }]
|
196
|
+
]);
|
197
|
+
|
198
|
+
// With associations
|
199
|
+
const postWithComments = await appFactories([
|
200
|
+
['create', 'post', 'with_comments', { comment_count: 5 }]
|
201
|
+
]);
|
202
|
+
|
203
|
+
// Building without saving
|
204
|
+
const userData = await appFactories([
|
205
|
+
['build', 'user', { name: 'Not Saved' }]
|
206
|
+
]);
|
207
|
+
});
|
208
|
+
```
|
209
|
+
|
210
|
+
### Using Attributes For
|
211
|
+
```js
|
212
|
+
test('get factory attributes', async ({ page }) => {
|
213
|
+
const attributes = await appFactories([
|
214
|
+
['attributes_for', 'user']
|
215
|
+
]);
|
216
|
+
|
217
|
+
// Use attributes to fill form
|
218
|
+
await page.fill('[name="user[name]"]', attributes[0].name);
|
219
|
+
await page.fill('[name="user[email]"]', attributes[0].email);
|
220
|
+
});
|
221
|
+
```
|
222
|
+
|
223
|
+
## Fixtures and Scenarios
|
224
|
+
|
225
|
+
### Loading Rails Fixtures
|
226
|
+
```js
|
227
|
+
test('load fixtures', async ({ page }) => {
|
228
|
+
await appFixtures();
|
229
|
+
|
230
|
+
await page.goto('/products');
|
231
|
+
// Fixtures are loaded
|
232
|
+
});
|
233
|
+
```
|
234
|
+
|
235
|
+
### Creating Scenarios
|
236
|
+
```ruby
|
237
|
+
# e2e/playwright/app_commands/scenarios/complex_setup.rb
|
238
|
+
# Create a complex test scenario
|
239
|
+
5.times do |i|
|
240
|
+
user = User.create!(
|
241
|
+
name: "User #{i}",
|
242
|
+
email: "user#{i}@example.com"
|
243
|
+
)
|
244
|
+
|
245
|
+
3.times do |j|
|
246
|
+
user.posts.create!(
|
247
|
+
title: "Post #{j} by User #{i}",
|
248
|
+
content: "Content for post #{j}",
|
249
|
+
published: j.even?
|
250
|
+
)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Add some comments
|
255
|
+
Post.published.each do |post|
|
256
|
+
2.times do
|
257
|
+
post.comments.create!(
|
258
|
+
author: "Commenter",
|
259
|
+
content: "Great post!"
|
260
|
+
)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
Using scenarios in tests:
|
266
|
+
```js
|
267
|
+
test('complex scenario', async ({ page }) => {
|
268
|
+
await appScenario('complex_setup');
|
269
|
+
|
270
|
+
await page.goto('/posts');
|
271
|
+
await expect(page.locator('.post')).toHaveCount(15);
|
272
|
+
await expect(page.locator('.published')).toHaveCount(7);
|
273
|
+
});
|
274
|
+
```
|
275
|
+
|
276
|
+
## Database Management
|
277
|
+
|
278
|
+
### Cleaning Between Tests
|
279
|
+
```js
|
280
|
+
// Global setup
|
281
|
+
test.beforeEach(async () => {
|
282
|
+
await appClean();
|
283
|
+
});
|
284
|
+
|
285
|
+
// Or selectively
|
286
|
+
test('with fresh database', async ({ page }) => {
|
287
|
+
await appClean();
|
288
|
+
await app('load_seed'); // Optionally load seeds
|
289
|
+
|
290
|
+
// Your test here
|
291
|
+
});
|
292
|
+
```
|
293
|
+
|
294
|
+
### Custom Clean Commands
|
295
|
+
```ruby
|
296
|
+
# e2e/playwright/app_commands/clean.rb
|
297
|
+
if defined?(DatabaseCleaner)
|
298
|
+
DatabaseCleaner.strategy = :truncation
|
299
|
+
DatabaseCleaner.clean
|
300
|
+
else
|
301
|
+
# Manual cleaning
|
302
|
+
tables = ActiveRecord::Base.connection.tables
|
303
|
+
tables.delete('schema_migrations')
|
304
|
+
tables.delete('ar_internal_metadata')
|
305
|
+
|
306
|
+
tables.each do |table|
|
307
|
+
ActiveRecord::Base.connection.execute("DELETE FROM #{table}")
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
# Reset sequences for PostgreSQL
|
312
|
+
if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
313
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
314
|
+
ActiveRecord::Base.connection.reset_pk_sequence!(table)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
Rails.cache.clear if Rails.cache
|
319
|
+
```
|
320
|
+
|
321
|
+
## Advanced Configuration
|
322
|
+
|
323
|
+
### Running Custom Ruby Code
|
324
|
+
```js
|
325
|
+
test('execute ruby code', async ({ page }) => {
|
326
|
+
// Run arbitrary Ruby code
|
327
|
+
const result = await appEval(`
|
328
|
+
User.count
|
329
|
+
`);
|
330
|
+
console.log('User count:', result);
|
331
|
+
|
332
|
+
// More complex evaluation
|
333
|
+
const stats = await appEval(`
|
334
|
+
{
|
335
|
+
users: User.count,
|
336
|
+
posts: Post.count,
|
337
|
+
latest_user: User.last&.name
|
338
|
+
}
|
339
|
+
`);
|
340
|
+
});
|
341
|
+
```
|
342
|
+
|
343
|
+
### Authentication for Commands
|
344
|
+
```js
|
345
|
+
// e2e/playwright/support/authenticated-on-rails.js
|
346
|
+
const TOKEN = process.env.CYPRESS_SECRET_TOKEN;
|
347
|
+
|
348
|
+
async function authenticatedCommand(name, options = {}) {
|
349
|
+
const context = await request.newContext();
|
350
|
+
const response = await context.post('/__e2e__/command', {
|
351
|
+
data: {
|
352
|
+
name,
|
353
|
+
options,
|
354
|
+
cypress_token: TOKEN
|
355
|
+
},
|
356
|
+
headers: {
|
357
|
+
'Content-Type': 'application/json'
|
358
|
+
}
|
359
|
+
});
|
360
|
+
|
361
|
+
if (response.status() === 401) {
|
362
|
+
throw new Error('Authentication failed');
|
363
|
+
}
|
364
|
+
|
365
|
+
return response.json();
|
366
|
+
}
|
367
|
+
```
|
368
|
+
|
369
|
+
### Parallel Testing
|
370
|
+
```js
|
371
|
+
// playwright.config.js
|
372
|
+
module.exports = {
|
373
|
+
workers: 4, // Run 4 tests in parallel
|
374
|
+
fullyParallel: true,
|
375
|
+
|
376
|
+
use: {
|
377
|
+
// Each worker gets unique database
|
378
|
+
baseURL: process.env.BASE_URL || 'http://localhost:5017',
|
379
|
+
},
|
380
|
+
|
381
|
+
globalSetup: './global-setup.js',
|
382
|
+
globalTeardown: './global-teardown.js'
|
383
|
+
};
|
384
|
+
|
385
|
+
// global-setup.js
|
386
|
+
module.exports = async config => {
|
387
|
+
// Setup databases for parallel workers
|
388
|
+
for (let i = 0; i < config.workers; i++) {
|
389
|
+
process.env[`TEST_ENV_NUMBER_${i}`] = i.toString();
|
390
|
+
}
|
391
|
+
};
|
392
|
+
```
|
393
|
+
|
394
|
+
## Migration from Cypress
|
395
|
+
|
396
|
+
### Command Comparison
|
397
|
+
|
398
|
+
| Cypress | Playwright |
|
399
|
+
|---------|------------|
|
400
|
+
| `cy.app('clean')` | `await app('clean')` |
|
401
|
+
| `cy.appFactories([...])` | `await appFactories([...])` |
|
402
|
+
| `cy.appScenario('name')` | `await appScenario('name')` |
|
403
|
+
| `cy.visit('/path')` | `await page.goto('/path')` |
|
404
|
+
| `cy.contains('text')` | `await expect(page.locator('text')).toBeVisible()` |
|
405
|
+
| `cy.get('.class')` | `page.locator('.class')` |
|
406
|
+
| `cy.click()` | `await locator.click()` |
|
407
|
+
|
408
|
+
### Converting Test Files
|
409
|
+
```js
|
410
|
+
// Cypress version
|
411
|
+
describe('Test', () => {
|
412
|
+
beforeEach(() => {
|
413
|
+
cy.app('clean');
|
414
|
+
cy.appFactories([
|
415
|
+
['create', 'user', { name: 'Test' }]
|
416
|
+
]);
|
417
|
+
});
|
418
|
+
|
419
|
+
it('works', () => {
|
420
|
+
cy.visit('/users');
|
421
|
+
cy.contains('Test');
|
422
|
+
});
|
423
|
+
});
|
424
|
+
|
425
|
+
// Playwright version
|
426
|
+
const { test, expect } = require('@playwright/test');
|
427
|
+
const { app, appFactories } = require('../support/on-rails');
|
428
|
+
|
429
|
+
test.describe('Test', () => {
|
430
|
+
test.beforeEach(async () => {
|
431
|
+
await app('clean');
|
432
|
+
await appFactories([
|
433
|
+
['create', 'user', { name: 'Test' }]
|
434
|
+
]);
|
435
|
+
});
|
436
|
+
|
437
|
+
test('works', async ({ page }) => {
|
438
|
+
await page.goto('/users');
|
439
|
+
await expect(page.locator('text=Test')).toBeVisible();
|
440
|
+
});
|
441
|
+
});
|
442
|
+
```
|
443
|
+
|
444
|
+
## Running Tests
|
445
|
+
|
446
|
+
### Using Rake Tasks
|
447
|
+
```bash
|
448
|
+
# Open Playwright UI
|
449
|
+
bin/rails playwright:open
|
450
|
+
|
451
|
+
# Run tests headless
|
452
|
+
bin/rails playwright:run
|
453
|
+
```
|
454
|
+
|
455
|
+
### Manual Execution
|
456
|
+
```bash
|
457
|
+
# Start Rails server
|
458
|
+
CYPRESS=1 bin/rails server -p 5017
|
459
|
+
|
460
|
+
# In another terminal
|
461
|
+
npx playwright test
|
462
|
+
|
463
|
+
# With specific browser
|
464
|
+
npx playwright test --project=chromium
|
465
|
+
|
466
|
+
# With UI mode
|
467
|
+
npx playwright test --ui
|
468
|
+
|
469
|
+
# Debug mode
|
470
|
+
npx playwright test --debug
|
471
|
+
```
|
472
|
+
|
473
|
+
### CI Configuration
|
474
|
+
```yaml
|
475
|
+
# .github/workflows/playwright.yml
|
476
|
+
name: Playwright Tests
|
477
|
+
on: [push, pull_request]
|
478
|
+
|
479
|
+
jobs:
|
480
|
+
test:
|
481
|
+
runs-on: ubuntu-latest
|
482
|
+
steps:
|
483
|
+
- uses: actions/checkout@v3
|
484
|
+
- uses: actions/setup-node@v3
|
485
|
+
- uses: ruby/setup-ruby@v1
|
486
|
+
with:
|
487
|
+
bundler-cache: true
|
488
|
+
|
489
|
+
- run: yarn install
|
490
|
+
- run: npx playwright install --with-deps
|
491
|
+
|
492
|
+
- run: bundle exec rails db:create db:schema:load
|
493
|
+
env:
|
494
|
+
RAILS_ENV: test
|
495
|
+
|
496
|
+
- run: bundle exec rails playwright:run
|
497
|
+
env:
|
498
|
+
RAILS_ENV: test
|
499
|
+
|
500
|
+
- uses: actions/upload-artifact@v3
|
501
|
+
if: failure()
|
502
|
+
with:
|
503
|
+
name: playwright-traces
|
504
|
+
path: test-results/
|
505
|
+
```
|
506
|
+
|
507
|
+
## Best Practices
|
508
|
+
|
509
|
+
1. **Always clean between tests** to ensure isolation
|
510
|
+
2. **Use Page Object Model** for complex applications
|
511
|
+
3. **Leverage Playwright's auto-waiting** instead of explicit waits
|
512
|
+
4. **Run tests in parallel** for faster CI
|
513
|
+
5. **Use fixtures for static data**, factories for dynamic data
|
514
|
+
6. **Commit test recordings** for debugging failures
|
515
|
+
|
516
|
+
## Debugging
|
517
|
+
|
518
|
+
### Enable Debug Mode
|
519
|
+
```bash
|
520
|
+
# Run with debug
|
521
|
+
PWDEBUG=1 npx playwright test
|
522
|
+
|
523
|
+
# This will:
|
524
|
+
# - Open browser in headed mode
|
525
|
+
# - Open Playwright Inspector
|
526
|
+
# - Pause at the start of each test
|
527
|
+
```
|
528
|
+
|
529
|
+
### Using Traces
|
530
|
+
```js
|
531
|
+
// Enable traces for debugging
|
532
|
+
const { chromium } = require('playwright');
|
533
|
+
|
534
|
+
test('debug this', async ({ page }, testInfo) => {
|
535
|
+
// Start tracing
|
536
|
+
await page.context().tracing.start({
|
537
|
+
screenshots: true,
|
538
|
+
snapshots: true
|
539
|
+
});
|
540
|
+
|
541
|
+
// Your test
|
542
|
+
await page.goto('/');
|
543
|
+
|
544
|
+
// Save trace
|
545
|
+
await page.context().tracing.stop({
|
546
|
+
path: `trace-${testInfo.title}.zip`
|
547
|
+
});
|
548
|
+
});
|
549
|
+
```
|
550
|
+
|
551
|
+
View traces:
|
552
|
+
```bash
|
553
|
+
npx playwright show-trace trace-debug-this.zip
|
554
|
+
```
|