fair-playwright 1.0.0 â 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -195
- package/dist/index.cjs +20 -0
- package/dist/index.d.cts +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +20 -0
- package/package.json +8 -3
- package/resources/fair-playwright-logo-vertical.png +0 -0
- package/resources/fair-playwright-logo.png +0 -0
- package/resources/terminal-output-example.png +0 -0
package/README.md
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="resources/fair-playwright-logo.png" alt="fair-playwright" width="500" />
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
#
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
AI-optimized Playwright test reporter with progressive terminal output and hierarchical step management
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/fair-playwright)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
7
12
|
|
|
8
13
|
## Features
|
|
9
14
|
|
|
10
|
-
- ðĪ **AI-First Design** - Structured output optimized for LLM context
|
|
11
|
-
- ð **MAJOR/MINOR Step Hierarchy** -
|
|
15
|
+
- ðĪ **AI-First Design** - Structured output optimized for LLM context
|
|
16
|
+
- ð **MAJOR/MINOR Step Hierarchy** - Two-level test organization
|
|
12
17
|
- ⥠**Progressive Terminal Output** - Live updates with smart compression
|
|
13
|
-
- ðŊ **Zero Config** -
|
|
14
|
-
-
|
|
15
|
-
- ð **Type Safe** - Full TypeScript support with exported types
|
|
16
|
-
- ð **MCP Server** - Built-in integration for AI coding assistants
|
|
18
|
+
- ðŊ **Zero Config** - Works out of the box
|
|
19
|
+
- ð **MCP Server** - Built-in AI assistant integration
|
|
17
20
|
|
|
18
21
|
## Installation
|
|
19
22
|
|
|
@@ -34,229 +37,97 @@ export default defineConfig({
|
|
|
34
37
|
});
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
### 2. Write Tests
|
|
40
|
+
### 2. Write Tests
|
|
38
41
|
|
|
39
|
-
**
|
|
42
|
+
**Quick Mode** (v1.1.0+) - Compact syntax:
|
|
40
43
|
|
|
41
44
|
```typescript
|
|
42
45
|
import { test } from '@playwright/test';
|
|
43
46
|
import { e2e } from 'fair-playwright';
|
|
44
47
|
|
|
45
48
|
test('user login', async ({ page }) => {
|
|
46
|
-
await e2e.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
await page.getByRole('button', { name: 'Login' }).click();
|
|
57
|
-
}, { success: 'Submitted', failure: 'Failed to submit' });
|
|
58
|
-
});
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
**Declarative Mode (Complex flows):**
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
await e2e.major('User login flow', {
|
|
65
|
-
success: 'User logged in successfully',
|
|
66
|
-
failure: 'Login failed',
|
|
67
|
-
steps: [
|
|
68
|
-
{
|
|
69
|
-
title: 'Open login page',
|
|
70
|
-
success: 'Page opened',
|
|
71
|
-
failure: 'Failed to open',
|
|
72
|
-
action: async () => {
|
|
73
|
-
await page.goto('/login');
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
title: 'Fill credentials',
|
|
78
|
-
success: 'Credentials filled',
|
|
79
|
-
failure: 'Failed to fill',
|
|
80
|
-
action: async () => {
|
|
81
|
-
await page.getByLabel('Email').fill('test@example.com');
|
|
82
|
-
await page.getByLabel('Password').fill('password123');
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
title: 'Submit form',
|
|
87
|
-
success: 'Form submitted',
|
|
88
|
-
failure: 'Failed to submit',
|
|
89
|
-
action: async () => {
|
|
90
|
-
await page.getByRole('button', { name: 'Login' }).click();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
]
|
|
49
|
+
await e2e.quick('User login flow', [
|
|
50
|
+
['Open login page', async () => {
|
|
51
|
+
await page.goto('/login');
|
|
52
|
+
}],
|
|
53
|
+
['Submit credentials', async () => {
|
|
54
|
+
await page.getByLabel('Email').fill('user@example.com');
|
|
55
|
+
await page.getByLabel('Password').fill('password');
|
|
56
|
+
await page.getByRole('button', { name: 'Login' }).click();
|
|
57
|
+
}]
|
|
58
|
+
]);
|
|
94
59
|
});
|
|
95
60
|
```
|
|
96
61
|
|
|
97
|
-
|
|
62
|
+
**Declarative Mode** - Full control:
|
|
98
63
|
|
|
99
64
|
```typescript
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
stepClassification: {
|
|
112
|
-
durationThreshold: 1000, // Steps > 1s are MAJOR
|
|
113
|
-
autoDetect: true
|
|
65
|
+
test('user login', async ({ page }) => {
|
|
66
|
+
await e2e.major('User login flow', {
|
|
67
|
+
success: 'User logged in successfully',
|
|
68
|
+
failure: 'Login failed',
|
|
69
|
+
steps: [
|
|
70
|
+
{
|
|
71
|
+
title: 'Open login page',
|
|
72
|
+
success: 'Page opened',
|
|
73
|
+
action: async () => {
|
|
74
|
+
await page.goto('/login');
|
|
75
|
+
}
|
|
114
76
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
77
|
+
{
|
|
78
|
+
title: 'Submit credentials',
|
|
79
|
+
success: 'Form submitted',
|
|
80
|
+
action: async () => {
|
|
81
|
+
await page.getByLabel('Email').fill('user@example.com');
|
|
82
|
+
await page.getByLabel('Password').fill('password');
|
|
83
|
+
await page.getByRole('button', { name: 'Login' }).click();
|
|
84
|
+
}
|
|
118
85
|
}
|
|
119
|
-
|
|
120
|
-
|
|
86
|
+
]
|
|
87
|
+
});
|
|
121
88
|
});
|
|
122
89
|
```
|
|
123
90
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
- **MAJOR steps**: High-level user flows (e.g., "User logs in", "Checkout process")
|
|
127
|
-
- **MINOR steps**: Detailed actions within a major step (e.g., "Fill email", "Click submit")
|
|
128
|
-
|
|
129
|
-
This two-level hierarchy helps both humans and AI understand test structure at a glance.
|
|
130
|
-
|
|
131
|
-
## MCP Server Integration
|
|
132
|
-
|
|
133
|
-
Fair-playwright includes a full **Model Context Protocol (MCP)** server for AI assistant integration.
|
|
91
|
+
### 3. Run Tests
|
|
134
92
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
The MCP server exposes:
|
|
138
|
-
- **3 Resources**: Test results, summary, and failure details
|
|
139
|
-
- **5 Tools**: Query tests, filter by status, get step details, and more
|
|
140
|
-
- **Streaming Support**: Real-time test result updates
|
|
141
|
-
|
|
142
|
-
### Setup with Claude Desktop
|
|
143
|
-
|
|
144
|
-
Add to `claude_desktop_config.json`:
|
|
145
|
-
|
|
146
|
-
```json
|
|
147
|
-
{
|
|
148
|
-
"mcpServers": {
|
|
149
|
-
"fair-playwright": {
|
|
150
|
-
"command": "npx",
|
|
151
|
-
"args": ["fair-playwright-mcp"],
|
|
152
|
-
"env": {
|
|
153
|
-
"FAIR_PLAYWRIGHT_RESULTS": "./test-results/results.json"
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
93
|
+
```bash
|
|
94
|
+
npx playwright test
|
|
158
95
|
```
|
|
159
96
|
|
|
160
|
-
|
|
97
|
+
## Documentation
|
|
161
98
|
|
|
162
|
-
|
|
163
|
-
- `fair-playwright://test-summary` - AI-optimized summary (Markdown)
|
|
164
|
-
- `fair-playwright://failures` - Detailed failure information (Markdown)
|
|
99
|
+
ð **Full Documentation**: https://baranaytass.github.io/fair-playwright/
|
|
165
100
|
|
|
166
|
-
|
|
101
|
+
- [Getting Started](https://baranaytass.github.io/fair-playwright/guide/getting-started)
|
|
102
|
+
- [API Reference](https://baranaytass.github.io/fair-playwright/api/)
|
|
103
|
+
- [Configuration](https://baranaytass.github.io/fair-playwright/guide/configuration)
|
|
104
|
+
- [MCP Integration](https://baranaytass.github.io/fair-playwright/guide/mcp)
|
|
105
|
+
- [Examples](https://baranaytass.github.io/fair-playwright/examples/)
|
|
167
106
|
|
|
168
|
-
|
|
169
|
-
- `get_failure_summary` - Get AI-optimized failure summary
|
|
170
|
-
- `query_test` - Search for specific test by title
|
|
171
|
-
- `get_tests_by_status` - Filter tests by passed/failed/skipped
|
|
172
|
-
- `get_step_details` - Get MAJOR/MINOR step details for a test
|
|
107
|
+
## MCP Server
|
|
173
108
|
|
|
174
|
-
|
|
109
|
+
Fair-playwright includes a Model Context Protocol server for AI assistants:
|
|
175
110
|
|
|
176
111
|
```bash
|
|
177
112
|
npx fair-playwright-mcp --help
|
|
178
|
-
npx fair-playwright-mcp --results-path ./custom-results.json --verbose
|
|
179
113
|
```
|
|
180
114
|
|
|
181
|
-
|
|
115
|
+
See [MCP Guide](https://baranaytass.github.io/fair-playwright/guide/mcp) for details.
|
|
182
116
|
|
|
183
|
-
|
|
117
|
+
## Examples
|
|
184
118
|
|
|
185
|
-
|
|
186
|
-
â Authentication (2/2 tests, 1.2s)
|
|
187
|
-
|
|
188
|
-
âģ Payment Processing (1/3 tests)
|
|
189
|
-
â Validate cart (234ms)
|
|
190
|
-
âģ Process payment (running 2.1s)
|
|
191
|
-
Step 1/4: Navigate to checkout â
|
|
192
|
-
Step 2/4: Fill payment form â
|
|
193
|
-
Step 3/4: Submit payment âģ
|
|
194
|
-
|
|
195
|
-
âļ Remaining: Order Confirmation (0/2 tests)
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### AI-Optimized Output (Markdown)
|
|
199
|
-
|
|
200
|
-
Generates structured markdown summaries perfect for AI analysis:
|
|
201
|
-
|
|
202
|
-
```markdown
|
|
203
|
-
# Test Results: E2E Payment Flow
|
|
204
|
-
|
|
205
|
-
**Status**: â FAILED (2/3 test suites passed)
|
|
206
|
-
**Duration**: 15.4s
|
|
207
|
-
|
|
208
|
-
## â
Authentication Suite (2/2 tests, 1.2s)
|
|
209
|
-
All tests passed.
|
|
210
|
-
|
|
211
|
-
## â Payment Processing (1/2 tests, 8.3s)
|
|
212
|
-
|
|
213
|
-
### â Payment Submission (5.1s - FAILED)
|
|
214
|
-
|
|
215
|
-
**Error**: Timeout waiting for element
|
|
216
|
-
**Location**: tests/payment.spec.ts:45:12
|
|
217
|
-
|
|
218
|
-
**Steps Executed**:
|
|
219
|
-
1. â
Navigate to /checkout (189ms)
|
|
220
|
-
2. â
Fill payment form (456ms)
|
|
221
|
-
3. â Click submit button - Element not found
|
|
119
|
+
Check out the [examples](./examples) directory for complete working examples.
|
|
222
120
|
|
|
223
|
-
|
|
224
|
-
- Screenshot: ./test-results/payment-fail-001.png
|
|
225
|
-
- Trace: ./test-results/trace.zip
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Why fair-playwright?
|
|
229
|
-
|
|
230
|
-
- **For Developers**: Progressive output keeps you focused on what matters
|
|
231
|
-
- **For AI**: Structured markdown summaries help AI understand test failures instantly
|
|
232
|
-
- **For Teams**: Hierarchical steps document test flows automatically
|
|
233
|
-
|
|
234
|
-
## Development
|
|
235
|
-
|
|
236
|
-
```bash
|
|
237
|
-
# Install dependencies
|
|
238
|
-
npm install
|
|
239
|
-
|
|
240
|
-
# Build
|
|
241
|
-
npm run build
|
|
242
|
-
|
|
243
|
-
# Run tests
|
|
244
|
-
npm test
|
|
121
|
+
## Contributing
|
|
245
122
|
|
|
246
|
-
|
|
247
|
-
npm run test:integration
|
|
248
|
-
```
|
|
123
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
249
124
|
|
|
250
125
|
## License
|
|
251
126
|
|
|
252
127
|
MIT ÂĐ [Baran Aytas](https://github.com/baranaytass)
|
|
253
128
|
|
|
254
|
-
## Contributing
|
|
255
|
-
|
|
256
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
257
|
-
|
|
258
129
|
## Links
|
|
259
130
|
|
|
260
|
-
- [
|
|
261
|
-
- [Issue Tracker](https://github.com/baranaytass/fair-playwright/issues)
|
|
131
|
+
- [Documentation](https://baranaytass.github.io/fair-playwright/)
|
|
262
132
|
- [npm Package](https://www.npmjs.com/package/fair-playwright)
|
|
133
|
+
- [GitHub Issues](https://github.com/baranaytass/fair-playwright/issues)
|
package/dist/index.cjs
CHANGED
|
@@ -925,6 +925,26 @@ var E2EHelperImpl = class {
|
|
|
925
925
|
}
|
|
926
926
|
});
|
|
927
927
|
}
|
|
928
|
+
/**
|
|
929
|
+
* Execute a quick workflow with simplified tuple syntax (v1.1.0+)
|
|
930
|
+
* Converts compact syntax to declarative mode internally
|
|
931
|
+
*/
|
|
932
|
+
async quick(title, steps, options) {
|
|
933
|
+
const stepDefinitions = steps.map((step) => {
|
|
934
|
+
const [stepTitle, action, stepOptions] = step;
|
|
935
|
+
return {
|
|
936
|
+
title: stepTitle,
|
|
937
|
+
action,
|
|
938
|
+
success: stepOptions?.success,
|
|
939
|
+
failure: stepOptions?.failure
|
|
940
|
+
};
|
|
941
|
+
});
|
|
942
|
+
return this.majorDeclarative(title, {
|
|
943
|
+
success: options?.success,
|
|
944
|
+
failure: options?.failure,
|
|
945
|
+
steps: stepDefinitions
|
|
946
|
+
});
|
|
947
|
+
}
|
|
928
948
|
/**
|
|
929
949
|
* Inline mode for major steps
|
|
930
950
|
*/
|
package/dist/index.d.cts
CHANGED
|
@@ -287,6 +287,16 @@ interface MajorStepOptions {
|
|
|
287
287
|
*/
|
|
288
288
|
steps?: StepDefinition[];
|
|
289
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Quick step definition - simplified tuple syntax
|
|
292
|
+
* [title, action] or [title, action, options]
|
|
293
|
+
*/
|
|
294
|
+
type QuickStepDefinition = [string, () => Promise<void>] | [string, () => Promise<void>, StepOptions];
|
|
295
|
+
/**
|
|
296
|
+
* Options for quick mode execution
|
|
297
|
+
*/
|
|
298
|
+
interface QuickModeOptions extends StepOptions {
|
|
299
|
+
}
|
|
290
300
|
/**
|
|
291
301
|
* E2E test helper interface
|
|
292
302
|
*/
|
|
@@ -303,6 +313,22 @@ interface E2EHelper {
|
|
|
303
313
|
* Execute a minor step
|
|
304
314
|
*/
|
|
305
315
|
minor(title: string, action: () => Promise<void>, options?: StepOptions): Promise<void>;
|
|
316
|
+
/**
|
|
317
|
+
* Execute a quick workflow with simplified syntax (v1.1.0+)
|
|
318
|
+
* Compact API for simple test cases
|
|
319
|
+
*
|
|
320
|
+
* @param title - Major step title
|
|
321
|
+
* @param steps - Array of [title, action] or [title, action, options] tuples
|
|
322
|
+
* @param options - Optional success/failure messages for major step
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* await e2e.quick('User login', [
|
|
326
|
+
* ['Open page', async () => { await page.goto('/login') }],
|
|
327
|
+
* ['Fill form', async () => { await page.fill('#email', 'test@example.com') }],
|
|
328
|
+
* ['Submit', async () => { await page.click('button[type="submit"]') }]
|
|
329
|
+
* ])
|
|
330
|
+
*/
|
|
331
|
+
quick(title: string, steps: QuickStepDefinition[], options?: QuickModeOptions): Promise<void>;
|
|
306
332
|
}
|
|
307
333
|
|
|
308
334
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -287,6 +287,16 @@ interface MajorStepOptions {
|
|
|
287
287
|
*/
|
|
288
288
|
steps?: StepDefinition[];
|
|
289
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Quick step definition - simplified tuple syntax
|
|
292
|
+
* [title, action] or [title, action, options]
|
|
293
|
+
*/
|
|
294
|
+
type QuickStepDefinition = [string, () => Promise<void>] | [string, () => Promise<void>, StepOptions];
|
|
295
|
+
/**
|
|
296
|
+
* Options for quick mode execution
|
|
297
|
+
*/
|
|
298
|
+
interface QuickModeOptions extends StepOptions {
|
|
299
|
+
}
|
|
290
300
|
/**
|
|
291
301
|
* E2E test helper interface
|
|
292
302
|
*/
|
|
@@ -303,6 +313,22 @@ interface E2EHelper {
|
|
|
303
313
|
* Execute a minor step
|
|
304
314
|
*/
|
|
305
315
|
minor(title: string, action: () => Promise<void>, options?: StepOptions): Promise<void>;
|
|
316
|
+
/**
|
|
317
|
+
* Execute a quick workflow with simplified syntax (v1.1.0+)
|
|
318
|
+
* Compact API for simple test cases
|
|
319
|
+
*
|
|
320
|
+
* @param title - Major step title
|
|
321
|
+
* @param steps - Array of [title, action] or [title, action, options] tuples
|
|
322
|
+
* @param options - Optional success/failure messages for major step
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* await e2e.quick('User login', [
|
|
326
|
+
* ['Open page', async () => { await page.goto('/login') }],
|
|
327
|
+
* ['Fill form', async () => { await page.fill('#email', 'test@example.com') }],
|
|
328
|
+
* ['Submit', async () => { await page.click('button[type="submit"]') }]
|
|
329
|
+
* ])
|
|
330
|
+
*/
|
|
331
|
+
quick(title: string, steps: QuickStepDefinition[], options?: QuickModeOptions): Promise<void>;
|
|
306
332
|
}
|
|
307
333
|
|
|
308
334
|
/**
|
package/dist/index.js
CHANGED
|
@@ -885,6 +885,26 @@ var E2EHelperImpl = class {
|
|
|
885
885
|
}
|
|
886
886
|
});
|
|
887
887
|
}
|
|
888
|
+
/**
|
|
889
|
+
* Execute a quick workflow with simplified tuple syntax (v1.1.0+)
|
|
890
|
+
* Converts compact syntax to declarative mode internally
|
|
891
|
+
*/
|
|
892
|
+
async quick(title, steps, options) {
|
|
893
|
+
const stepDefinitions = steps.map((step) => {
|
|
894
|
+
const [stepTitle, action, stepOptions] = step;
|
|
895
|
+
return {
|
|
896
|
+
title: stepTitle,
|
|
897
|
+
action,
|
|
898
|
+
success: stepOptions?.success,
|
|
899
|
+
failure: stepOptions?.failure
|
|
900
|
+
};
|
|
901
|
+
});
|
|
902
|
+
return this.majorDeclarative(title, {
|
|
903
|
+
success: options?.success,
|
|
904
|
+
failure: options?.failure,
|
|
905
|
+
steps: stepDefinitions
|
|
906
|
+
});
|
|
907
|
+
}
|
|
888
908
|
/**
|
|
889
909
|
* Inline mode for major steps
|
|
890
910
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fair-playwright",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "AI-optimized Playwright test reporter with progressive terminal output and hierarchical step management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
21
|
+
"resources",
|
|
21
22
|
"README.md",
|
|
22
23
|
"LICENSE"
|
|
23
24
|
],
|
|
@@ -34,7 +35,10 @@
|
|
|
34
35
|
"typecheck": "tsc --noEmit",
|
|
35
36
|
"changeset": "changeset",
|
|
36
37
|
"release": "changeset publish",
|
|
37
|
-
"prepublishOnly": "npm run build"
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
39
|
+
"docs:dev": "vitepress dev docs",
|
|
40
|
+
"docs:build": "vitepress build docs",
|
|
41
|
+
"docs:preview": "vitepress preview docs"
|
|
38
42
|
},
|
|
39
43
|
"keywords": [
|
|
40
44
|
"playwright",
|
|
@@ -58,7 +62,7 @@
|
|
|
58
62
|
"bugs": {
|
|
59
63
|
"url": "https://github.com/baranaytass/fair-playwright/issues"
|
|
60
64
|
},
|
|
61
|
-
"homepage": "https://github.
|
|
65
|
+
"homepage": "https://baranaytass.github.io/fair-playwright/",
|
|
62
66
|
"author": "Baran Aytas",
|
|
63
67
|
"license": "MIT",
|
|
64
68
|
"peerDependencies": {
|
|
@@ -80,6 +84,7 @@
|
|
|
80
84
|
"prettier": "^3.4.2",
|
|
81
85
|
"tsup": "^8.3.5",
|
|
82
86
|
"typescript": "^5.7.2",
|
|
87
|
+
"vitepress": "^1.6.4",
|
|
83
88
|
"vitest": "^2.1.8"
|
|
84
89
|
},
|
|
85
90
|
"engines": {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|