@saucebase/laravel-playwright 1.0.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/LICENSE +21 -0
- package/README.md +290 -0
- package/dist/index.d.ts +52 -0
- package/dist/laravel-playwright.js +76 -0
- package/dist/laravel-playwright.umd.cjs +5 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Saucebase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Laravel Playwright
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
This repository contains a Laravel and a Playwright library to help you write E2E tests for your Laravel application using [Playwright](https://playwright.dev/). It adds a set of endpoints to your Laravel application to allow Playwright to interact with it. You can do the following from your Playwright tests:
|
|
5
|
+
|
|
6
|
+
- Run artisan commands
|
|
7
|
+
- Create models using factories
|
|
8
|
+
- Run database queries
|
|
9
|
+
- Run PHP functions
|
|
10
|
+
- Update Laravel config while a test is running (until the test ends and calls `tearDown`).
|
|
11
|
+
- Register a boot function to run on every subsequent Laravel request in the test โ useful for swapping service bindings.
|
|
12
|
+
- Traveling to a specific time in the application during the test
|
|
13
|
+
|
|
14
|
+
## ๐ฆ Installation
|
|
15
|
+
|
|
16
|
+
On Laravel side, install the package via composer:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
composer require --dev saucebase/laravel-playwright
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
On Playwright side, install the package via npm:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @saucebase/laravel-playwright --save-dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## โ๏ธ Laravel Config
|
|
29
|
+
|
|
30
|
+
Publish the config file:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
php artisan vendor:publish --tag=laravel-playwright-config
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This creates `config/laravel-playwright.php` with the following options:
|
|
37
|
+
|
|
38
|
+
```php
|
|
39
|
+
return [
|
|
40
|
+
/**
|
|
41
|
+
* The prefix for the testing endpoints used to interact with Playwright.
|
|
42
|
+
* Make sure to update `use.laravelBaseUrl` in playwright.config.ts if you change this.
|
|
43
|
+
*/
|
|
44
|
+
'prefix' => env('PLAYWRIGHT_PREFIX', 'playwright'),
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The environments in which the testing endpoints are enabled.
|
|
48
|
+
* CAUTION: Enabling testing endpoints in production is a critical security issue.
|
|
49
|
+
*/
|
|
50
|
+
'environments' => ['local', 'testing'],
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Optional secret token to authenticate Playwright requests.
|
|
54
|
+
* Set PLAYWRIGHT_SECRET in your .env and laravelSecret in playwright.config.ts.
|
|
55
|
+
*/
|
|
56
|
+
'secret' => env('PLAYWRIGHT_SECRET', null),
|
|
57
|
+
];
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## ๐ก Example
|
|
61
|
+
|
|
62
|
+
If you need a good and more complex example of a repository using this package, look at https://github.com/saucebase-dev/saucebase
|
|
63
|
+
|
|
64
|
+
## ๐ญ Playwright Config
|
|
65
|
+
|
|
66
|
+
Set `use.laravelBaseUrl` in your `playwright.config.ts` to the base URL of your testing endpoints (your application URL + the `prefix` from Laravel config).
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export default defineConfig({
|
|
70
|
+
/** ...other config */
|
|
71
|
+
use: {
|
|
72
|
+
laravelBaseUrl: 'http://localhost/playwright',
|
|
73
|
+
laravelSecret: process.env.PLAYWRIGHT_SECRET, // optional
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If you use TypeScript, include the `LaravelOptions` type in the `defineConfig` function.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import type { LaravelOptions } from '@saucebase/laravel-playwright';
|
|
82
|
+
|
|
83
|
+
export default defineConfig<LaravelOptions>({
|
|
84
|
+
use: {
|
|
85
|
+
laravelBaseUrl: 'http://localhost/playwright',
|
|
86
|
+
laravelSecret: process.env.PLAYWRIGHT_SECRET, // optional
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## ๐ Security
|
|
92
|
+
|
|
93
|
+
The package supports an optional shared secret to prevent unauthorized access to the testing endpoints.
|
|
94
|
+
|
|
95
|
+
**Laravel `.env`:**
|
|
96
|
+
```
|
|
97
|
+
PLAYWRIGHT_SECRET=some-secret
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**`playwright.config.ts`:**
|
|
101
|
+
```ts
|
|
102
|
+
use: {
|
|
103
|
+
laravelSecret: process.env.PLAYWRIGHT_SECRET,
|
|
104
|
+
},
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
When configured, the TypeScript client sends the secret as an `X-Playwright-Secret` header on every request. Laravel returns `401 Unauthorized` if the header is missing or doesn't match. When `PLAYWRIGHT_SECRET` is not set, all requests pass through (backwards compatible).
|
|
108
|
+
|
|
109
|
+
## ๐งช Setting up tests
|
|
110
|
+
|
|
111
|
+
In your Playwright tests, swap the `test` import from `@playwright/test` to `@saucebase/laravel-playwright`.
|
|
112
|
+
|
|
113
|
+
```diff
|
|
114
|
+
- import { test } from '@playwright/test';
|
|
115
|
+
+ import { test } from '@saucebase/laravel-playwright';
|
|
116
|
+
|
|
117
|
+
test('example', async ({ laravel }) => {
|
|
118
|
+
await laravel.artisan('migrate:fresh');
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
> **Note**: In practice, it is not recommended to import from `@saucebase/laravel-playwright` directly in every test file if you have many. Instead, create your own [test fixture](https://playwright.dev/docs/test-fixtures) extending `test` from `@saucebase/laravel-playwright` and import that fixture in your tests.
|
|
123
|
+
|
|
124
|
+
## ๐ Basic Usage
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { test } from '@saucebase/laravel-playwright';
|
|
128
|
+
|
|
129
|
+
test('example', async ({ laravel }) => {
|
|
130
|
+
|
|
131
|
+
/** ๐ RUN ARTISAN COMMANDS */
|
|
132
|
+
const output = await laravel.artisan('migrate:fresh');
|
|
133
|
+
/**
|
|
134
|
+
* output.code: number - The exit code of the command
|
|
135
|
+
* output.output: string - The output of the command
|
|
136
|
+
*/
|
|
137
|
+
/** with parameters */
|
|
138
|
+
await laravel.artisan('db:seed', ['--class', 'DatabaseSeeder']);
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/** ๐๏ธ TRUNCATE TABLES */
|
|
142
|
+
await laravel.truncate();
|
|
143
|
+
/** in specific DB connections */
|
|
144
|
+
await laravel.truncate(['connection1', 'connection2']);
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
/** ๐ญ CREATE MODELS FROM FACTORIES */
|
|
148
|
+
/**
|
|
149
|
+
* Create a App\Models\User model
|
|
150
|
+
* user will be an object of the model
|
|
151
|
+
*/
|
|
152
|
+
const user = await laravel.factory('User');
|
|
153
|
+
/** Create a App\Models\User model with attributes */
|
|
154
|
+
await laravel.factory('User', { name: 'John Doe' });
|
|
155
|
+
/**
|
|
156
|
+
* Create 5 App\Models\User models
|
|
157
|
+
* users will be an array of the models
|
|
158
|
+
*/
|
|
159
|
+
const users = await laravel.factory('User', {}, 5);
|
|
160
|
+
/** Create a CustomModel model */
|
|
161
|
+
await laravel.factory('CustomModel');
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
/** ๐พ RUN A DATABASE QUERY */
|
|
165
|
+
await laravel.query('DELETE FROM users');
|
|
166
|
+
/** with bindings */
|
|
167
|
+
await laravel.query('DELETE FROM users WHERE id = ?', [1]);
|
|
168
|
+
/** on a specific connection */
|
|
169
|
+
await laravel.query('DELETE FROM users', [], { connection: 'connection1' });
|
|
170
|
+
/** unprepared statement */
|
|
171
|
+
await laravel.query(`
|
|
172
|
+
DROP SCHEMA public CASCADE;
|
|
173
|
+
CREATE SCHEMA public;
|
|
174
|
+
GRANT ALL ON SCHEMA public TO public;
|
|
175
|
+
`, [], { unprepared: true });
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
/** ๐ RUN A SELECT QUERY */
|
|
179
|
+
/** returns an array of objects */
|
|
180
|
+
const blogs = await laravel.select('SELECT * FROM blogs');
|
|
181
|
+
/** with bindings */
|
|
182
|
+
await laravel.select('SELECT * FROM blogs WHERE id = ?', [1]);
|
|
183
|
+
/** on a specific connection */
|
|
184
|
+
await laravel.select('SELECT * FROM blogs', {}, { connection: 'connection1' });
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
/** โ๏ธ RUN A PHP FUNCTION */
|
|
188
|
+
/**
|
|
189
|
+
* Output is JSON encoded in Laravel and decoded in Playwright
|
|
190
|
+
* The following examples call this function:
|
|
191
|
+
* function sayHello($name) { return "Hello, $name!"; }
|
|
192
|
+
*/
|
|
193
|
+
const funcOutput = await laravel.callFunction('sayHello');
|
|
194
|
+
/** with positional parameters */
|
|
195
|
+
await laravel.callFunction('sayHello', ['John']);
|
|
196
|
+
/** with named parameters */
|
|
197
|
+
await laravel.callFunction('sayHello', { name: 'John' });
|
|
198
|
+
/** static class method */
|
|
199
|
+
await laravel.callFunction("App\\MyAwesomeClass::method");
|
|
200
|
+
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## ๐ Dynamic Configuration
|
|
207
|
+
|
|
208
|
+
You can update Laravel config for **ALL** subsequent requests until the test ends.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { test } from '@saucebase/laravel-playwright';
|
|
212
|
+
|
|
213
|
+
test('example', async ({ laravel }) => {
|
|
214
|
+
|
|
215
|
+
/** ๐ง SET DYNAMIC CONFIG */
|
|
216
|
+
/**
|
|
217
|
+
* Persists for all subsequent requests until the test ends
|
|
218
|
+
* and tearDown is called (done automatically)
|
|
219
|
+
*/
|
|
220
|
+
await laravel.config('app.timezone', 'America/Sao_Paulo');
|
|
221
|
+
|
|
222
|
+
/** โฐ TRAVEL TO A TIME */
|
|
223
|
+
/** similar to Laravel's `travelTo` method */
|
|
224
|
+
await laravel.travel('2022-01-01 12:00:00');
|
|
225
|
+
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## ๐ Boot Functions
|
|
230
|
+
|
|
231
|
+
Boot functions let you run PHP code **at the start of every subsequent Laravel request** during a test. This is the right tool when you need a service binding or side effect to be in place for browser-driven requests โ not just during test setup.
|
|
232
|
+
|
|
233
|
+
A common use case is swapping a real external service (payment gateway, mailer, SMS provider) with a fake for the duration of a test.
|
|
234
|
+
|
|
235
|
+
**1. Create a helper class in your app** (e.g. `app/E2EHelpers.php`):
|
|
236
|
+
|
|
237
|
+
```php
|
|
238
|
+
<?php
|
|
239
|
+
|
|
240
|
+
namespace App;
|
|
241
|
+
|
|
242
|
+
use App\Services\PaymentGateway;
|
|
243
|
+
use App\Services\FakePaymentGateway;
|
|
244
|
+
use App\Services\Mailer;
|
|
245
|
+
use App\Services\NullMailer;
|
|
246
|
+
|
|
247
|
+
class E2EHelpers
|
|
248
|
+
{
|
|
249
|
+
/**
|
|
250
|
+
* Swap real services for fakes. Called at boot on every request in the test.
|
|
251
|
+
*/
|
|
252
|
+
public static function useFakeServices(): void
|
|
253
|
+
{
|
|
254
|
+
app()->bind(PaymentGateway::class, FakePaymentGateway::class);
|
|
255
|
+
app()->bind(Mailer::class, NullMailer::class);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**2. Register it in your Playwright test:**
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { test } from '@saucebase/laravel-playwright';
|
|
264
|
+
|
|
265
|
+
test('checkout with fake payment gateway', async ({ laravel, page }) => {
|
|
266
|
+
|
|
267
|
+
// Register once โ runs at boot on every subsequent request this test makes
|
|
268
|
+
await laravel.registerBootFunction('App\\E2EHelpers::useFakeServices');
|
|
269
|
+
|
|
270
|
+
// From here, every page load and API call the browser makes will use the fakes
|
|
271
|
+
const user = await laravel.factory('User');
|
|
272
|
+
await laravel.artisan('db:seed', ['--class', 'ProductSeeder']);
|
|
273
|
+
|
|
274
|
+
await page.goto('/checkout');
|
|
275
|
+
await page.fill('[name="card_number"]', '4242424242424242');
|
|
276
|
+
await page.click('button[type="submit"]');
|
|
277
|
+
|
|
278
|
+
await expect(page.locator('.order-confirmation')).toBeVisible();
|
|
279
|
+
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
> **Why not use `callFunction` instead?**
|
|
284
|
+
> `callFunction` runs once and returns. `registerBootFunction` runs at the start of **every** request the browser makes during the test, so service bindings registered in the boot phase are in place for the full request lifecycle โ including middleware, controllers, and event listeners.
|
|
285
|
+
|
|
286
|
+
> **Note:** The helper class only needs to exist in your `local` and `testing` environments. You can guard it with an environment check or keep it out of production deployments entirely.
|
|
287
|
+
|
|
288
|
+
## ๐ Credits
|
|
289
|
+
|
|
290
|
+
This package was inspired by [hyvor/laravel-playwright](https://github.com/hyvor/laravel-playwright)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { APIRequestContext } from '@playwright/test';
|
|
2
|
+
export interface LaravelOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The base URL to the endpoints
|
|
5
|
+
* @default <playwright-base-url>/playwright
|
|
6
|
+
*/
|
|
7
|
+
laravelBaseUrl: string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Optional secret token sent as X-Playwright-Secret header
|
|
10
|
+
*/
|
|
11
|
+
laravelSecret: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
interface LaravelFixtures {
|
|
14
|
+
laravel: Laravel;
|
|
15
|
+
}
|
|
16
|
+
export declare const test: import('playwright/test').TestType<import('playwright/test').PlaywrightTestArgs & import('playwright/test').PlaywrightTestOptions & LaravelFixtures & LaravelOptions, import('playwright/test').PlaywrightWorkerArgs & import('playwright/test').PlaywrightWorkerOptions>;
|
|
17
|
+
export declare class Laravel {
|
|
18
|
+
private baseUrl;
|
|
19
|
+
private request;
|
|
20
|
+
private secret;
|
|
21
|
+
constructor(baseUrl: string, request: APIRequestContext, secret?: string | undefined);
|
|
22
|
+
call<T = unknown>(endpoint: string, data?: object): Promise<T>;
|
|
23
|
+
artisan(command: string, parameters?: string[]): Promise<{
|
|
24
|
+
code: number;
|
|
25
|
+
output: string;
|
|
26
|
+
}>;
|
|
27
|
+
truncate(connections?: (string | null)[]): Promise<unknown>;
|
|
28
|
+
factory(model: string, attrs?: Record<string, unknown>, count?: undefined): Promise<Record<string, unknown>>;
|
|
29
|
+
factory(model: string, attrs: Record<string, unknown>, count: number): Promise<Record<string, unknown>[]>;
|
|
30
|
+
query(query: string, bindings?: unknown[], options?: {
|
|
31
|
+
connection?: string | null;
|
|
32
|
+
unprepared?: boolean;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
success: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
select(query: string, bindings?: Record<string, unknown>, options?: {
|
|
37
|
+
connection?: string | null;
|
|
38
|
+
}): Promise<Record<string, unknown>[]>;
|
|
39
|
+
callFunction<T = unknown>(func: string, args?: unknown[] | Record<string, unknown>): Promise<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Sets a laravel config value until tearDown is called (or the test ends)
|
|
42
|
+
*/
|
|
43
|
+
config(key: string, value: unknown): Promise<unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* Travel to a specific time
|
|
46
|
+
* ex: travel('2021-01-01 00:00:00')
|
|
47
|
+
*/
|
|
48
|
+
travel(to: string): Promise<unknown>;
|
|
49
|
+
registerBootFunction(func: string): Promise<unknown>;
|
|
50
|
+
tearDown(): Promise<unknown>;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { test as i } from "@playwright/test";
|
|
2
|
+
const u = i.extend({
|
|
3
|
+
laravelBaseUrl: [void 0, { option: !0 }],
|
|
4
|
+
laravelSecret: [void 0, { option: !0 }],
|
|
5
|
+
laravel: async ({ laravelBaseUrl: s, laravelSecret: t, baseURL: a, request: e }, n) => {
|
|
6
|
+
const r = s || a + "/playwright", c = new l(r, e, t);
|
|
7
|
+
await n(c), await c.tearDown();
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
class l {
|
|
11
|
+
constructor(t, a, e = void 0) {
|
|
12
|
+
this.baseUrl = t, this.request = a, this.secret = e;
|
|
13
|
+
}
|
|
14
|
+
async call(t, a = {}) {
|
|
15
|
+
const e = this.baseUrl.replace(/\/$/, "") + t, n = { Accept: "application/json" };
|
|
16
|
+
this.secret && (n["X-Playwright-Secret"] = this.secret);
|
|
17
|
+
const r = await this.request.post(e, { data: a, headers: n });
|
|
18
|
+
if (r.status() !== 200)
|
|
19
|
+
throw new Error(`
|
|
20
|
+
Failed to call Laravel ${t}.
|
|
21
|
+
Status: ${r.status()}
|
|
22
|
+
Response: ${await r.text()}
|
|
23
|
+
`);
|
|
24
|
+
return await r.json();
|
|
25
|
+
}
|
|
26
|
+
async artisan(t, a = []) {
|
|
27
|
+
return await this.call("/artisan", { command: t, parameters: a });
|
|
28
|
+
}
|
|
29
|
+
async truncate(t = []) {
|
|
30
|
+
return await this.call("/truncate", { connections: t });
|
|
31
|
+
}
|
|
32
|
+
async factory(t, a = {}, e) {
|
|
33
|
+
return await this.call("/factory", { model: t, count: e, attrs: a });
|
|
34
|
+
}
|
|
35
|
+
async query(t, a = [], e = {}) {
|
|
36
|
+
const { connection: n = null, unprepared: r = !1 } = e;
|
|
37
|
+
if (r && a.length > 0)
|
|
38
|
+
throw new Error("Cannot use unprepared with bindings");
|
|
39
|
+
return await this.call("/query", {
|
|
40
|
+
query: t,
|
|
41
|
+
bindings: a,
|
|
42
|
+
connection: n,
|
|
43
|
+
unprepared: r
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async select(t, a = {}, e = {}) {
|
|
47
|
+
const { connection: n = null } = e;
|
|
48
|
+
return await this.call("/select", { query: t, bindings: a, connection: n });
|
|
49
|
+
}
|
|
50
|
+
async callFunction(t, a = []) {
|
|
51
|
+
return await this.call("/function", { function: t, args: a });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Sets a laravel config value until tearDown is called (or the test ends)
|
|
55
|
+
*/
|
|
56
|
+
async config(t, a) {
|
|
57
|
+
return await this.call("/dynamicConfig", { key: t, value: a });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Travel to a specific time
|
|
61
|
+
* ex: travel('2021-01-01 00:00:00')
|
|
62
|
+
*/
|
|
63
|
+
async travel(t) {
|
|
64
|
+
return await this.call("/travel", { to: t });
|
|
65
|
+
}
|
|
66
|
+
async registerBootFunction(t) {
|
|
67
|
+
return await this.call("/registerBootFunction", { function: t });
|
|
68
|
+
}
|
|
69
|
+
async tearDown() {
|
|
70
|
+
return await this.call("/tearDown");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
l as Laravel,
|
|
75
|
+
u as test
|
|
76
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
(function(r,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("@playwright/test")):typeof define=="function"&&define.amd?define(["exports","@playwright/test"],i):(r=typeof globalThis<"u"?globalThis:r||self,i(r.LaravelPlaywright={},r.PlaywrightTest))})(this,(function(r,i){"use strict";const u=i.test.extend({laravelBaseUrl:[void 0,{option:!0}],laravelSecret:[void 0,{option:!0}],laravel:async({laravelBaseUrl:l,laravelSecret:t,baseURL:e,request:a},s)=>{const n=l||e+"/playwright",o=new c(n,a,t);await s(o),await o.tearDown()}});class c{constructor(t,e,a=void 0){this.baseUrl=t,this.request=e,this.secret=a}async call(t,e={}){const a=this.baseUrl.replace(/\/$/,"")+t,s={Accept:"application/json"};this.secret&&(s["X-Playwright-Secret"]=this.secret);const n=await this.request.post(a,{data:e,headers:s});if(n.status()!==200)throw new Error(`
|
|
2
|
+
Failed to call Laravel ${t}.
|
|
3
|
+
Status: ${n.status()}
|
|
4
|
+
Response: ${await n.text()}
|
|
5
|
+
`);return await n.json()}async artisan(t,e=[]){return await this.call("/artisan",{command:t,parameters:e})}async truncate(t=[]){return await this.call("/truncate",{connections:t})}async factory(t,e={},a){return await this.call("/factory",{model:t,count:a,attrs:e})}async query(t,e=[],a={}){const{connection:s=null,unprepared:n=!1}=a;if(n&&e.length>0)throw new Error("Cannot use unprepared with bindings");return await this.call("/query",{query:t,bindings:e,connection:s,unprepared:n})}async select(t,e={},a={}){const{connection:s=null}=a;return await this.call("/select",{query:t,bindings:e,connection:s})}async callFunction(t,e=[]){return await this.call("/function",{function:t,args:e})}async config(t,e){return await this.call("/dynamicConfig",{key:t,value:e})}async travel(t){return await this.call("/travel",{to:t})}async registerBootFunction(t){return await this.call("/registerBootFunction",{function:t})}async tearDown(){return await this.call("/tearDown")}}r.Laravel=c,r.test=u,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saucebase/laravel-playwright",
|
|
3
|
+
"version": "v1.0.0",
|
|
4
|
+
"description": "Blends Laravel and Playwright into seamless E2E tests โ factories, queries, artisan commands, and time travel.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/saucebase-dev/laravel-playwright"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/saucebase-dev/laravel-playwright/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/saucebase-dev/laravel-playwright",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && vite build"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^25.3.1",
|
|
20
|
+
"typescript": "^5.9.3",
|
|
21
|
+
"vite": "^7.3.1"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@playwright/test": "^1.58.2"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/laravel-playwright.js",
|
|
30
|
+
"require": "./dist/laravel-playwright.umd.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"vite-plugin-dts": "^4.5.4"
|
|
38
|
+
}
|
|
39
|
+
}
|