cabloy 5.1.141 → 5.1.142
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/.cabloy-version +1 -1
- package/.github/workflows/playwright-e2e.yml +2 -2
- package/CHANGELOG.md +21 -0
- package/cabloy-docs/frontend/form-guide.md +47 -0
- package/cabloy-docs/frontend/markdown-guide.md +50 -17
- package/cabloy-docs/frontend/route-alias-guide.md +20 -0
- package/cabloy-docs/frontend/scripts.md +9 -11
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +25 -0
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +1 -1
- package/cabloy-docs/fullstack/quickstart.md +5 -4
- package/cabloy-docs/reference/repo-scripts.md +29 -42
- package/e2e/config/playwright.config.ts +34 -0
- package/e2e/scripts/e2e.ts +2 -27
- package/e2e/scripts/runE2e.ts +62 -0
- package/e2e/scripts/runE2eArgs.test.ts +75 -0
- package/e2e/scripts/runE2eArgs.ts +158 -0
- package/e2e/specs/{a-commerce/commerce.spec.ts → a-commerce.spec.ts} +361 -30
- package/e2e/specs/{cabloy-basic/account.spec.ts → account.spec.ts} +1 -2
- package/oxlint.config.ts +3 -0
- package/package.json +3 -9
- package/scripts/upgrade.ts +63 -68
- package/vona/packages-utils/lint/package.json +1 -1
- package/vona/packages-utils/lint/src/oxc/lint.ts +1 -1
- package/vona/packages-utils/lint/src/oxc/lintVue.ts +1 -1
- package/vona/pnpm-lock.yaml +41 -41
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/refundRecoveryView.tsx +7 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +31 -15
- package/vona/src/suite/a-home/modules/home-user/package.json +1 -0
- package/vona/src/suite/a-home/modules/home-user/src/service/account.ts +15 -2
- package/vona/src/suite/a-home/modules/home-user/test/accountActivation.test.ts +34 -4
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/package.json +2 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/.metadata/index.ts +26 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-markdown/src/bean/imageScene.markdown.ts +13 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +23 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/refundOperation.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +1 -1
- package/vona/src/suite-vendor/a-pay/package.json +5 -5
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/bean/resource/locale/type.ts +1 -0
- package/zova/pnpm-lock.yaml +63 -18
- package/zova/src/front/config/config/config.cabloyBasicWeb.ts +3 -1
- package/zova/src/front/config/config/config.cabloyCommerce.ts +4 -2
- package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +64 -63
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +6 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionRefund/controller.tsx +413 -134
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +76 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +67 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +7 -7
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +46 -9
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +50 -30
- package/zova/src/suite/cabloy-basic/modules/basic-captcha/src/component/formFieldCaptcha/controller.tsx +6 -2
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +9 -15
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/package.json +10 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/index.ts +11 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/.metadata/locales.ts +7 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +723 -5
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +461 -2
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/en-us.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/zh-cn.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/codeBlockLanguages.ts +42 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/lib/richTextContentStyle.ts +20 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/test/lib/codeBlockLanguages.test.ts +25 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +1 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +26 -12
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +1 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockMockPayment.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockMockPayment.ts +8 -1
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +13 -1
- package/zova/src/suite-vendor/a-pay/package.json +3 -3
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +2 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -6
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
- package/e2e/config/playwright.basic.config.ts +0 -3
- package/e2e/config/playwright.commerce.config.ts +0 -3
- package/e2e/config/playwright.shared.config.ts +0 -38
- package/e2e/scripts/testE2eClean.ts +0 -56
- /package/e2e/specs/{cabloy-basic/basic.spec.ts → cabloy-basic.spec.ts} +0 -0
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.142
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
- name: install Chromium
|
|
28
28
|
run: npx playwright install --with-deps chromium
|
|
29
29
|
- name: run Basic E2E baseline
|
|
30
|
-
run: npm run test:e2e
|
|
30
|
+
run: npm run test:e2e -- cabloy-basic account
|
|
31
31
|
- name: upload Playwright artifacts
|
|
32
32
|
if: failure()
|
|
33
33
|
uses: actions/upload-artifact@v7
|
|
@@ -65,7 +65,7 @@ jobs:
|
|
|
65
65
|
- name: synchronize Vona dependencies
|
|
66
66
|
run: npm run deps:vona
|
|
67
67
|
- name: run Commerce E2E baseline
|
|
68
|
-
run: npm run test:e2e
|
|
68
|
+
run: npm run test:e2e a-commerce
|
|
69
69
|
- name: upload Playwright artifacts
|
|
70
70
|
if: failure()
|
|
71
71
|
uses: actions/upload-artifact@v7
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.142
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add commerce specifications and refund support.
|
|
8
|
+
- Add address form and consumer URL support.
|
|
9
|
+
- Add captcha support.
|
|
10
|
+
- Add `LocalePattern` support.
|
|
11
|
+
- Add Markdown code blocks, images, links, basic formatting, and tables.
|
|
12
|
+
- Add `onFieldStateReady` documentation.
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
- Fix JSX linting issues.
|
|
17
|
+
|
|
18
|
+
### Improvements
|
|
19
|
+
|
|
20
|
+
- Refactor end-to-end tests.
|
|
21
|
+
- Regenerate the Vona lockfile.
|
|
22
|
+
- Update account and type definitions.
|
|
23
|
+
|
|
3
24
|
## 5.1.141
|
|
4
25
|
|
|
5
26
|
### Features
|
|
@@ -200,6 +200,53 @@ Use this when you need to:
|
|
|
200
200
|
- intercept input and blur behavior yourself
|
|
201
201
|
- mix Zova form state with a custom render contract
|
|
202
202
|
|
|
203
|
+
### Initialize external state with `onFieldStateReady`
|
|
204
|
+
|
|
205
|
+
A custom field sometimes has two state surfaces:
|
|
206
|
+
|
|
207
|
+
- the `ControllerFormField` state owned by `ZForm`
|
|
208
|
+
- state owned by the custom renderer or an external resource, such as a widget value or a challenge response
|
|
209
|
+
|
|
210
|
+
Use `onFieldStateReady` when the custom field needs to synchronize those surfaces after Zova has prepared or reset the field:
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
<ZFormField
|
|
214
|
+
name="externalValue"
|
|
215
|
+
onFieldStateReady={formField => {
|
|
216
|
+
if (this.externalValue !== undefined) {
|
|
217
|
+
formField.setValue(this.externalValue);
|
|
218
|
+
}
|
|
219
|
+
}}
|
|
220
|
+
slotDefault={({ props }, formField) => {
|
|
221
|
+
return (
|
|
222
|
+
<input
|
|
223
|
+
{...props}
|
|
224
|
+
value={formField.field.state.value ?? ''}
|
|
225
|
+
onInput={e => {
|
|
226
|
+
const value = (e.target as HTMLInputElement).value;
|
|
227
|
+
this.externalValue = value;
|
|
228
|
+
formField.setValue(value);
|
|
229
|
+
}}
|
|
230
|
+
onBlur={() => formField.handleBlur()}
|
|
231
|
+
></input>
|
|
232
|
+
);
|
|
233
|
+
}}
|
|
234
|
+
></ZFormField>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The callback receives the current `ControllerFormField` instance. It is a field-state lifecycle notification, not a DOM or VNode mounted hook, an asynchronous API-completion callback, or a general replacement for `nextTick`.
|
|
238
|
+
|
|
239
|
+
Zova invokes it in two important situations:
|
|
240
|
+
|
|
241
|
+
1. after the field controller has created its field binding and finished its initialization;
|
|
242
|
+
2. after a schema-property change updates the field options and calls `resetField(this.name)`.
|
|
243
|
+
|
|
244
|
+
The second timing is important: write the external value in the callback **after** the reset, because a write made before `resetField(...)` can be overwritten. If an external API later creates or refreshes the value, synchronize that API result explicitly as well; `onFieldStateReady` does not fetch, retry, or observe external resources for you.
|
|
245
|
+
|
|
246
|
+
Use input handlers or `formField.setValue(...)` for ordinary value changes, `formField.handleBlur()` for blur state, and `onEffect` when the field needs a value-change effect. `onFieldStateReady` is a replay point for external state after field initialization or reset, not a per-value-change listener.
|
|
247
|
+
|
|
248
|
+
For the source-level ordering and the relationship between field readiness and rendering, see [Zova Form Under the Hood](/frontend/zova-form-under-the-hood).
|
|
249
|
+
|
|
203
250
|
### `ZFormFieldBlank`
|
|
204
251
|
|
|
205
252
|
Use `ZFormFieldBlank` when the row is **not** a real data field.
|
|
@@ -16,6 +16,7 @@ Use this page together with:
|
|
|
16
16
|
- [Component Guide](/frontend/component-guide)
|
|
17
17
|
- [SSR ClientOnly](/frontend/ssr-client-only)
|
|
18
18
|
- [Backend Markdown Guide](/backend/markdown-guide)
|
|
19
|
+
- [Image Guide](/frontend/image-guide)
|
|
19
20
|
|
|
20
21
|
> [!TIP]
|
|
21
22
|
> **Choose the component by data contract**
|
|
@@ -63,12 +64,22 @@ export class EntityArticleContent {
|
|
|
63
64
|
}
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
The
|
|
67
|
+
The field accepts an optional `imageScene` option. When it is omitted, the editor uploads through the public `a-markdown:markdown` scene supplied by the `a-markdown` module:
|
|
67
68
|
|
|
68
69
|
```typescript
|
|
69
70
|
ZovaRender.field('basic-markdown:formFieldMarkdown');
|
|
70
71
|
```
|
|
71
72
|
|
|
73
|
+
A business module can override that default when its images need resource-specific authorization, ownership metadata, delivery behavior, or upload limits:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
ZovaRender.field('basic-markdown:formFieldMarkdown', {
|
|
77
|
+
imageScene: 'article:bodyImage',
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The configured scene must be registered by the backend and must return a durable `http` or `https` URL. Define scene policy, authentication, provider, and upload constraints through the image contract described in the [Image Guide](/frontend/image-guide); do not construct storage URLs in the editor.
|
|
82
|
+
|
|
72
83
|
The field value remains Markdown. It is not the editor's HTML output, and it should not be replaced with `innerHTML` or a browser DOM serialization.
|
|
73
84
|
|
|
74
85
|
A field can keep the derived HTML hidden from the editor-facing form while still exposing it in a separate public DTO. The Commerce Catalog entity follows this shape:
|
|
@@ -90,11 +101,11 @@ The exact persistence and DTO design belongs to the business module. Markdown do
|
|
|
90
101
|
|
|
91
102
|
`ZFormFieldMarkdown` is a Zova component with the normal Controller/Render/Style split.
|
|
92
103
|
|
|
93
|
-
| Zova role | Responsibility in this component
|
|
94
|
-
| ---------- |
|
|
95
|
-
| Controller | Owns the TipTap `Editor`, Markdown `value`, `readonly` state, form callbacks, and lifecycle |
|
|
96
|
-
| Render | Composes the standard `ZFormField`, field shell,
|
|
97
|
-
| Style | Creates the shared rich-text CSS class used by the editor and HTML display component
|
|
104
|
+
| Zova role | Responsibility in this component |
|
|
105
|
+
| ---------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| Controller | Owns the TipTap `Editor`, Markdown `value`, `readonly` state, toolbar commands/state, form callbacks, and lifecycle |
|
|
107
|
+
| Render | Composes the standard `ZFormField`, field shell, client-only toolbar, and `EditorContent` |
|
|
108
|
+
| Style | Creates the shared rich-text CSS class used by the editor and HTML display component |
|
|
98
109
|
|
|
99
110
|
This is a Markdown editor integrated with `ZFormField`; it is not a generic HTML editor API.
|
|
100
111
|
|
|
@@ -103,7 +114,7 @@ This is a Markdown editor integrated with `ZFormField`; it is not a generic HTML
|
|
|
103
114
|
The Controller creates the browser-dependent TipTap editor in `$controllerMounted`. Its extension set includes:
|
|
104
115
|
|
|
105
116
|
- Markdown
|
|
106
|
-
- StarterKit
|
|
117
|
+
- StarterKit, with its plain code-block node replaced by `CodeBlockLowlight`
|
|
107
118
|
- nested `TaskList` and `TaskItem`
|
|
108
119
|
- Image
|
|
109
120
|
- TableKit
|
|
@@ -119,6 +130,27 @@ editable: !this.readonly,
|
|
|
119
130
|
|
|
120
131
|
The editor DOM receives the current component style class and a minimum-height/padding class. The outer field shell uses Cabloy Basic's DaisyUI-oriented classes and adds an error border when the host form field is invalid.
|
|
121
132
|
|
|
133
|
+
### Toolbar
|
|
134
|
+
|
|
135
|
+
When the field is editable, the same `ClientOnly` boundary renders a toolbar above the editor. It provides controls for:
|
|
136
|
+
|
|
137
|
+
- undo and redo;
|
|
138
|
+
- paragraph and heading levels 1–6;
|
|
139
|
+
- bold, italic, strike-through, inline code, and highlight;
|
|
140
|
+
- bullet, ordered, and task lists;
|
|
141
|
+
- blockquotes, code blocks, and horizontal rules;
|
|
142
|
+
- applying, updating, and removing links on the current text selection through a URL prompt;
|
|
143
|
+
- uploading one image through the configured image scene and inserting the finalized image at the preserved selection;
|
|
144
|
+
- inserting a table through an 8 × 8 floating size picker. Moving over a cell previews the rectangle from the top-left cell to that cell (for example, 2 × 3); clicking inserts that many rows and columns. Inserted tables retain a header row.
|
|
145
|
+
|
|
146
|
+
The toolbar uses native buttons and a labelled block-style select, preserves the current editor selection while buttons are clicked, exposes active and unavailable states accessibly, and runs commands on the existing TipTap editor. Image upload delegates policy loading, validation, ordinary/direct transfer, and finalization to `basic-image`. Only a finalized URL using `http` or `https` is inserted, with the source filename as image alt text when available. The image node serializes through `getMarkdown()` just like every other editor command; it does not introduce an HTML value path. To add a link, select text and enter a URL; when the cursor is in an existing link, the same action edits its URL. Submitting an empty URL removes an existing link, while cancelling leaves the document unchanged. TipTap validates the URL before applying it, but backend Markdown rendering and sanitization remain the authoritative display security boundary. The table picker also supports keyboard navigation, Enter/Space to insert, and Escape to cancel. In readonly mode, TipTap remains visible but the toolbar and picker are omitted.
|
|
147
|
+
|
|
148
|
+
When the selection is inside an editable table, a separate contextual toolbar appears above that table. It adds and deletes rows or columns around the current cell, and can delete the table. The menu shares the same `ClientOnly`, selection-preservation, accessibility, readonly, and Markdown-serialization guarantees as the persistent toolbar. Column resizing remains out of scope.
|
|
149
|
+
|
|
150
|
+
When the cursor is inside an editable code block, a floating code-block toolbar appears above the block's `<pre>` element. It contains the language selector and supports Bash, CSS, C++, C#, Go, HTML, Java, JavaScript, JSON, Markdown, PHP, Python, Rust, Shell, SQL, and TypeScript, plus Plain text. The selector remains usable while focused and disappears after leaving the code block or entering readonly mode. Changing a language updates the fenced-code information string through TipTap's document attribute and persists as Markdown; lowlight token spans are an editor-only presentation detail and are never submitted as HTML. Existing unknown fence languages remain unchanged until the author explicitly selects a supported language or Plain text.
|
|
151
|
+
|
|
152
|
+
The toolbars intentionally do not include remote image import, a color picker, or a configurable plugin registry. Use the configured image scene for uploads; remote-image import needs its own explicit authorization and persistence contract. Link editing uses the existing Markdown link and backend sanitization contracts.
|
|
153
|
+
|
|
122
154
|
### Editor-to-form flow
|
|
123
155
|
|
|
124
156
|
When TipTap emits an update, the controller calls:
|
|
@@ -148,8 +180,8 @@ When the component is disposed, the controller calls `editor.destroy()` so TipTa
|
|
|
148
180
|
TipTap's `EditorContent` is browser-dependent. The editor is therefore inside Zova's `ClientOnly` component:
|
|
149
181
|
|
|
150
182
|
- SSR renders a minimum-height placeholder;
|
|
151
|
-
- the browser creates the editor after mounting;
|
|
152
|
-
- the editor should not be described as
|
|
183
|
+
- the browser creates the toolbar and editor after mounting;
|
|
184
|
+
- the editor and toolbar should not be described as SSR-rendered interactive controls.
|
|
153
185
|
|
|
154
186
|
The read-only HTML display path is different: it does not need TipTap or `ClientOnly` because it receives already-generated HTML.
|
|
155
187
|
|
|
@@ -223,7 +255,7 @@ const ready = true;
|
|
|
223
255
|

|
|
224
256
|
````
|
|
225
257
|
|
|
226
|
-
The
|
|
258
|
+
The interactive editor decorates supported code blocks with lowlight token spans for syntax highlighting. The backend projection continues to render semantic `<code class="language-*">` output from Markdown; it does not persist editor token spans. Do not infer support for arbitrary raw HTML, iframe embeds, inline styles, `data:` URLs, or custom classes from the presence of a TipTap extension.
|
|
227
259
|
|
|
228
260
|
## Common mistakes
|
|
229
261
|
|
|
@@ -239,8 +271,8 @@ The exact HTML output is governed by the backend renderer and sanitizer. Do not
|
|
|
239
271
|
4. **Expecting task checkboxes to be interactive**
|
|
240
272
|
- The backend transforms rendered task inputs into disabled checkboxes for display.
|
|
241
273
|
|
|
242
|
-
5. **Expecting
|
|
243
|
-
- The
|
|
274
|
+
5. **Expecting remote image import or unrelated plugin workflows from the toolbar**
|
|
275
|
+
- The toolbar supports configured image-scene uploads and Markdown link editing through a URL prompt, but it does not supply remote image import, a color picker, or a configurable plugin registry.
|
|
244
276
|
|
|
245
277
|
6. **Treating browser validation or presentation CSS as security**
|
|
246
278
|
- The backend renderer/sanitizer is the relevant content boundary. External image availability, privacy, CSP, and application authorization remain separate concerns.
|
|
@@ -252,10 +284,11 @@ Before integrating Markdown into a Zova feature, verify:
|
|
|
252
284
|
1. The editable field is a string containing Markdown.
|
|
253
285
|
2. The field metadata uses `basic-markdown:formFieldMarkdown`.
|
|
254
286
|
3. The form value is updated through the editor's Markdown API, not HTML.
|
|
255
|
-
4. Blur and readonly behavior still pass through the standard `ZFormField` contract.
|
|
256
|
-
5.
|
|
257
|
-
6.
|
|
258
|
-
7. Public
|
|
259
|
-
8.
|
|
287
|
+
4. Blur and readonly behavior still pass through the standard `ZFormField` contract, and readonly mode omits toolbar controls.
|
|
288
|
+
5. Toolbar commands keep the value in Markdown through the editor API rather than creating an HTML submission path.
|
|
289
|
+
6. The interactive toolbar and editor are inside the expected `ClientOnly` boundary.
|
|
290
|
+
7. Public display uses `ZMarkdownHtml` only with server-derived sanitized HTML.
|
|
291
|
+
8. Public DTOs do not expose editable Markdown unless that is intentional.
|
|
292
|
+
9. The backend write path regenerates the HTML projection for every create, update, import, or backfill path that can change Markdown.
|
|
260
293
|
|
|
261
294
|
For the backend API and security details, continue with [Backend Markdown Guide](/backend/markdown-guide).
|
|
@@ -54,6 +54,26 @@ The distinction matters after an alias has a documented reason:
|
|
|
54
54
|
|
|
55
55
|
Do not introduce a static route name solely to use `routes.name`: a static exception can use `routes.path` instead.
|
|
56
56
|
|
|
57
|
+
## Locale-aware aliases
|
|
58
|
+
|
|
59
|
+
For a public route whose URL includes an optional locale segment, use the shared `LocalePattern` exported by Zova instead of hard-coding one language or copying a regular expression:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { LocalePattern } from 'zova';
|
|
63
|
+
|
|
64
|
+
config.routes = {
|
|
65
|
+
name: {
|
|
66
|
+
'commerce-catalog:product': {
|
|
67
|
+
alias: `/:locale(${LocalePattern})?/product/:id`,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The current pattern accepts the common `xx` and `xx-yy` forms, such as `en-us`, `zh-cn`, and `fr-fr`. It defines the URL shape; it is not a whitelist of installed languages. The languages that the application actually offers still come from `config.locale.items` and the corresponding locale resources. When adding a language, update those locale registrations and resources, but do not duplicate the language in each route alias.
|
|
74
|
+
|
|
75
|
+
When the route declares `meta.locale: true`, `$router.getAliasPath(..., { params: { locale: true } })` uses the current locale and omits the configured default locale from an optional locale segment. Thus, with `en-us` as the default, the same alias can produce `/product/42` for `en-us` and `/zh-cn/product/42` for `zh-cn`.
|
|
76
|
+
|
|
57
77
|
## Generate a configured alias path
|
|
58
78
|
|
|
59
79
|
When application code needs a user-facing URL for a named route, use the canonical route name with `$router.getAliasPath(...)`:
|
|
@@ -61,28 +61,26 @@ npm run build:zova:admin
|
|
|
61
61
|
# Both default Basic sites, then a managed local run
|
|
62
62
|
npm run build:zova
|
|
63
63
|
npm run deps:vona
|
|
64
|
-
npm run test:e2e
|
|
64
|
+
npm run test:e2e cabloy-basic account
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
The managed clean command resets Vona-managed test data and the local Redis namespace, starts one development Vona worker, and runs the
|
|
67
|
+
The managed clean command resets Vona-managed test data and the local Redis namespace, starts one development Vona worker, and runs the selected specs. Browser commands consume already-built artifacts; they do not rebuild them.
|
|
68
68
|
|
|
69
|
-
The
|
|
69
|
+
The unified runner uses two commands:
|
|
70
70
|
|
|
71
71
|
```text
|
|
72
|
-
test:e2e
|
|
73
|
-
test:e2e
|
|
74
|
-
test:e2e:<suite>:admin all @admin surface scenarios
|
|
75
|
-
test:e2e:<suite>:clean managed clean local suite run
|
|
72
|
+
npm run test:e2e clean local run with database reset
|
|
73
|
+
npm run test:e2e:fast fast run without automatic reset
|
|
76
74
|
```
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
Place flat spec basenames directly after the npm script name. Use npm's `--` delimiter before Playwright options. With no names, every spec in `e2e/specs` runs. Native `--grep` and `--grep-invert` remain available, and repeatable `--tag` values require all listed tags:
|
|
79
77
|
|
|
80
78
|
```bash
|
|
81
|
-
npm run test:e2e
|
|
82
|
-
npm run test:e2e:
|
|
79
|
+
npm run test:e2e cabloy-basic account -- --grep @flow
|
|
80
|
+
npm run test:e2e:fast a-commerce -- --tag @web --tag @smoke
|
|
83
81
|
```
|
|
84
82
|
|
|
85
|
-
For the complete tag vocabulary, managed-runner argument boundaries, and externally managed-target examples, see [Repo Scripts](/reference/repo-scripts#ssr-browser-checks).
|
|
83
|
+
For the complete tag vocabulary, managed-runner argument boundaries, and externally managed-target examples, see [Repo Scripts](/reference/repo-scripts#ssr-browser-checks). A separately managed target uses `E2E_BASE_URL` with `test:e2e:fast`; the caller owns external-target data, cache, and artifact freshness.
|
|
86
84
|
|
|
87
85
|
## Cabloy Start root wrappers
|
|
88
86
|
|
|
@@ -293,6 +293,31 @@ A practical reading takeaway is:
|
|
|
293
293
|
- **the form owns the cross-field runtime**
|
|
294
294
|
- **each field controller owns one field’s TanStack binding and render context**
|
|
295
295
|
|
|
296
|
+
### Field-state readiness and custom synchronization
|
|
297
|
+
|
|
298
|
+
The field options contract declares `onFieldStateReady` in:
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Its callback receives the current `ControllerFormField<TParentData>` instance. The callback is owned by the field controller; it is not a DOM ref, a VNode mounted event, or a notification that an external API has completed.
|
|
305
|
+
|
|
306
|
+
`ControllerFormField.__init__()` invokes the callback at the end of field initialization, after the field binding, computed props, behavior holder, and optional client-side `onEffect` watcher have been prepared. The same controller also watches the field's schema property. When that property changes materially, the sequence is:
|
|
307
|
+
|
|
308
|
+
```text
|
|
309
|
+
_getFormFieldOptions()
|
|
310
|
+
-> _formField.api.update(options)
|
|
311
|
+
-> form.resetField(name)
|
|
312
|
+
-> onFieldStateReady(formField)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The callback must follow `resetField(...)`. A custom field may hold a value outside the form controller—for example, a widget value or an externally created challenge object—and a value written before the reset can be replaced by the reset's default value. The callback provides the replay point where the custom field can write its current external value back through `formField.setValue(...)` or the owning form setter.
|
|
316
|
+
|
|
317
|
+
This lifecycle is separate from external data changes. If an API creates or refreshes the external value later, synchronize that result in the API completion path too. Likewise, user input should continue to update the field through `formField.setValue(...)`, while value-change effects belong to the normal field effect/input surfaces. `onFieldStateReady` is not a per-value-change listener and does not fetch, retry, or wait for a renderer DOM node.
|
|
318
|
+
|
|
319
|
+
For the public authoring shape and a renderer-neutral example, see [Initialize external state with `onFieldStateReady`](/frontend/form-guide#initialize-external-state-with-onfieldstateready).
|
|
320
|
+
|
|
296
321
|
## 7. How one field is configured
|
|
297
322
|
|
|
298
323
|
The field controller builds its runtime configuration mainly through:
|
|
@@ -77,7 +77,7 @@ Use commands from the active repository root.
|
|
|
77
77
|
|
|
78
78
|
| Edition | Marker | Frontend command | Managed clean E2E |
|
|
79
79
|
| ------------ | ------------------ | -------------------------------------------------- | ------------------------------ |
|
|
80
|
-
| Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e
|
|
80
|
+
| Cabloy Basic | `__CABLOY_BASIC__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e` |
|
|
81
81
|
| Cabloy Start | `__CABLOY_START__` | `npm run dev:zova:admin` or `npm run dev:zova:web` | `npm run test:e2e:start:clean` |
|
|
82
82
|
|
|
83
83
|
Run one frontend command, not both, in each worktree. Both managed clean E2E workflows read Vona's effective local `SERVER_LISTEN_PORT`, then reset, start, and target that local runtime.
|
|
@@ -109,16 +109,17 @@ npm run upgrade:dry-run
|
|
|
109
109
|
npm run upgrade
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
In Cabloy Basic, upgrade synchronizes the framework-owned SSR browser E2E baseline,
|
|
112
|
+
In Cabloy Basic, upgrade synchronizes the framework-owned SSR browser E2E baseline, the two root E2E scripts, and the `@playwright/test` development dependency. The framework owns these paths:
|
|
113
113
|
|
|
114
114
|
```text
|
|
115
115
|
e2e/config/
|
|
116
116
|
e2e/scripts/
|
|
117
|
-
e2e/specs/cabloy-basic
|
|
118
|
-
e2e/specs/
|
|
117
|
+
e2e/specs/cabloy-basic.spec.ts
|
|
118
|
+
e2e/specs/account.spec.ts
|
|
119
|
+
e2e/specs/a-commerce.spec.ts
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
Keep project-owned browser
|
|
122
|
+
Keep additional project-owned browser specs under other filenames in `e2e/specs`. The current baseline is a fresh-project contract; unsupported legacy layouts are not repaired by the upgrader.
|
|
122
123
|
|
|
123
124
|
### Cabloy Start repository baseline
|
|
124
125
|
|
|
@@ -33,14 +33,8 @@ Cabloy Basic exposes these shared root scripts:
|
|
|
33
33
|
- `npm run start:one`
|
|
34
34
|
- `npm run test`
|
|
35
35
|
- `npm run db:reset`
|
|
36
|
-
- `npm run test:e2e
|
|
37
|
-
- `npm run test:e2e:
|
|
38
|
-
- `npm run test:e2e:basic:admin`
|
|
39
|
-
- `npm run test:e2e:basic:clean`
|
|
40
|
-
- `npm run test:e2e:commerce`
|
|
41
|
-
- `npm run test:e2e:commerce:web`
|
|
42
|
-
- `npm run test:e2e:commerce:admin`
|
|
43
|
-
- `npm run test:e2e:commerce:clean`
|
|
36
|
+
- `npm run test:e2e`
|
|
37
|
+
- `npm run test:e2e:fast`
|
|
44
38
|
- `npm run tsc`
|
|
45
39
|
- `npm run docs:dev`
|
|
46
40
|
- `npm run docs:build`
|
|
@@ -88,11 +82,12 @@ Basic upgrade owns these browser baseline paths:
|
|
|
88
82
|
```text
|
|
89
83
|
e2e/config/
|
|
90
84
|
e2e/scripts/
|
|
91
|
-
e2e/specs/cabloy-basic
|
|
92
|
-
e2e/specs/
|
|
85
|
+
e2e/specs/cabloy-basic.spec.ts
|
|
86
|
+
e2e/specs/account.spec.ts
|
|
87
|
+
e2e/specs/a-commerce.spec.ts
|
|
93
88
|
```
|
|
94
89
|
|
|
95
|
-
It also reconciles the framework
|
|
90
|
+
It also reconciles the two framework E2E scripts and the `@playwright/test` development dependency. Keep additional project browser specs under other filenames in `e2e/specs`; the upgrader updates only the listed framework files. The current fresh baseline is required and is not repaired for unsupported legacy project layouts.
|
|
96
91
|
|
|
97
92
|
### Cabloy Start private repository
|
|
98
93
|
|
|
@@ -108,44 +103,38 @@ The public-package upgrade flow does not source or reconcile the Start baseline,
|
|
|
108
103
|
|
|
109
104
|
## SSR browser checks
|
|
110
105
|
|
|
111
|
-
The
|
|
106
|
+
The unified runner has two modes:
|
|
112
107
|
|
|
113
|
-
- `test:e2e
|
|
114
|
-
- `
|
|
115
|
-
- `test:e2e:<suite>:clean` resets managed local state, starts one development Vona worker, then runs the suite or a Playwright-filtered subset.
|
|
108
|
+
- `npm run test:e2e`: clean local run; checks the managed port, resets the database, starts one development Vona worker, and runs Playwright.
|
|
109
|
+
- `npm run test:e2e:fast`: skips the reset for quick reruns and may target either the local managed server or an externally managed `E2E_BASE_URL`.
|
|
116
110
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Pass Playwright options after npm's `--` delimiter. `@web` and `@admin` are stable surface tags. Purpose tags depend on the suite: current scenarios use `@smoke`, while the Basic suite also uses `@flow`. ATP IDs remain in titles for exact evidence and failure reruns.
|
|
120
|
-
|
|
121
|
-
### Cabloy Basic and Commerce
|
|
122
|
-
|
|
123
|
-
The Basic suite exercises Web at `/` and Admin at `/admin` through Vona's SSR dispatcher. Prepare fresh SSR and REST artifacts explicitly when frontend output has changed:
|
|
111
|
+
Place spec basenames directly after the npm script name; use npm's `--` delimiter only before Playwright options. Multiple spec names are allowed. With no names, every spec in `e2e/specs` is discovered:
|
|
124
112
|
|
|
125
113
|
```bash
|
|
126
|
-
npm run
|
|
127
|
-
npm run
|
|
128
|
-
npm run test:e2e:
|
|
114
|
+
npm run test:e2e cabloy-basic account
|
|
115
|
+
npm run test:e2e a-commerce
|
|
116
|
+
npm run test:e2e:fast account
|
|
117
|
+
npm run test:e2e:fast a-commerce -- --grep ATP-SSR
|
|
118
|
+
npm run test:e2e:fast a-commerce -- --grep-invert @admin
|
|
129
119
|
```
|
|
130
120
|
|
|
121
|
+
Tags remain independent from filenames. Repeat `--tag` to require all tags, while native `--grep` and `--grep-invert` remain available:
|
|
122
|
+
|
|
131
123
|
```bash
|
|
132
|
-
|
|
133
|
-
npm run test:e2e:
|
|
124
|
+
npm run test:e2e:fast a-commerce -- --tag @web --tag @smoke
|
|
125
|
+
npm run test:e2e:fast account -- --grep @flow --tag @web
|
|
126
|
+
```
|
|
134
127
|
|
|
135
|
-
|
|
136
|
-
npm run test:e2e:basic:clean -- --grep @flow
|
|
137
|
-
npm run test:e2e:basic:clean -- --grep @admin
|
|
128
|
+
The existing tags include `@web`, `@admin`, `@smoke`, `@flow`, `@ssr`, `@theme`, and the business tags used by Commerce such as `@cart`, `@payment`, `@shipment`, and `@refund`. No suite tag is required. Clean runs are local-only and reject `E2E_BASE_URL`; fast runs against an external target do not reset, start, stop, or rebuild that target.
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
npm run test:e2e:basic:clean -- --grep '(?=.*@admin)(?=.*@flow)'
|
|
141
|
-
```
|
|
130
|
+
### Cabloy Basic and Commerce
|
|
142
131
|
|
|
143
|
-
|
|
132
|
+
The Basic baseline exercises Web at `/` and Admin at `/admin` through Vona's SSR dispatcher. Prepare artifacts when frontend output has changed:
|
|
144
133
|
|
|
145
134
|
```bash
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
npm run build:zova
|
|
136
|
+
npm run deps:vona
|
|
137
|
+
npm run test:e2e cabloy-basic account
|
|
149
138
|
```
|
|
150
139
|
|
|
151
140
|
Commerce browser acceptance exercises Customer Web at `/commerce` and Operator Admin routing at `/commerce-admin`. Prepare its paired artifacts explicitly:
|
|
@@ -153,15 +142,13 @@ Commerce browser acceptance exercises Customer Web at `/commerce` and Operator A
|
|
|
153
142
|
```bash
|
|
154
143
|
npm run build:zova:commerce
|
|
155
144
|
npm run deps:vona
|
|
156
|
-
npm run test:e2e
|
|
145
|
+
npm run test:e2e a-commerce
|
|
157
146
|
```
|
|
158
147
|
|
|
159
|
-
For
|
|
148
|
+
For a separately managed target, use the fast command:
|
|
160
149
|
|
|
161
150
|
```bash
|
|
162
|
-
|
|
163
|
-
COMMERCE_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:commerce:web
|
|
164
|
-
COMMERCE_E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:commerce -- --grep @smoke
|
|
151
|
+
E2E_BASE_URL=http://127.0.0.1:7102 npm run test:e2e:fast a-commerce -- --tag @smoke
|
|
165
152
|
```
|
|
166
153
|
|
|
167
154
|
### Cabloy Start
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
import { E2E_LOCAL_BASE_URL, E2E_ROOT_DIR } from '../scripts/e2e.ts';
|
|
4
|
+
|
|
5
|
+
const externalBaseURL = process.env.E2E_BASE_URL;
|
|
6
|
+
const baseURL = externalBaseURL || E2E_LOCAL_BASE_URL;
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
testDir: `${E2E_ROOT_DIR}/e2e/specs`,
|
|
10
|
+
fullyParallel: false,
|
|
11
|
+
workers: 1,
|
|
12
|
+
forbidOnly: !!process.env.CI,
|
|
13
|
+
retries: process.env.CI ? 2 : 0,
|
|
14
|
+
reporter: process.env.CI ? [['html', { open: 'never' }], ['list']] : 'list',
|
|
15
|
+
use: {
|
|
16
|
+
baseURL,
|
|
17
|
+
trace: 'on-first-retry',
|
|
18
|
+
},
|
|
19
|
+
webServer: externalBaseURL
|
|
20
|
+
? undefined
|
|
21
|
+
: {
|
|
22
|
+
command: 'node e2e/scripts/startE2eVona.ts',
|
|
23
|
+
cwd: E2E_ROOT_DIR,
|
|
24
|
+
url: `${baseURL}/`,
|
|
25
|
+
timeout: 180_000,
|
|
26
|
+
reuseExistingServer: false,
|
|
27
|
+
stdout: 'pipe',
|
|
28
|
+
stderr: 'pipe',
|
|
29
|
+
gracefulShutdown: {
|
|
30
|
+
signal: 'SIGINT',
|
|
31
|
+
timeout: 10_000,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
package/e2e/scripts/e2e.ts
CHANGED
|
@@ -16,30 +16,5 @@ if (!Number.isInteger(e2ePort) || e2ePort <= 0 || e2ePort > 65535) {
|
|
|
16
16
|
|
|
17
17
|
export const E2E_PORT = e2ePort;
|
|
18
18
|
export const E2E_LOCAL_BASE_URL = `http://127.0.0.1:${E2E_PORT}`;
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const E2E_SPECS_DIR = resolve(E2E_ROOT_DIR, 'e2e', 'specs');
|
|
22
|
-
|
|
23
|
-
const e2eSuites = {
|
|
24
|
-
basic: {
|
|
25
|
-
externalBaseUrlEnv: 'BASIC_E2E_BASE_URL',
|
|
26
|
-
configFile: resolve(E2E_CONFIG_DIR, 'playwright.basic.config.ts'),
|
|
27
|
-
testDir: resolve(E2E_SPECS_DIR, 'cabloy-basic'),
|
|
28
|
-
readinessPath: '/',
|
|
29
|
-
},
|
|
30
|
-
commerce: {
|
|
31
|
-
externalBaseUrlEnv: 'COMMERCE_E2E_BASE_URL',
|
|
32
|
-
configFile: resolve(E2E_CONFIG_DIR, 'playwright.commerce.config.ts'),
|
|
33
|
-
testDir: resolve(E2E_SPECS_DIR, 'a-commerce'),
|
|
34
|
-
readinessPath: '/commerce',
|
|
35
|
-
},
|
|
36
|
-
} as const;
|
|
37
|
-
|
|
38
|
-
export type E2eSuiteName = keyof typeof e2eSuites;
|
|
39
|
-
|
|
40
|
-
export function getE2eSuite(name: string | undefined) {
|
|
41
|
-
if (name && name in e2eSuites) {
|
|
42
|
-
return e2eSuites[name as E2eSuiteName];
|
|
43
|
-
}
|
|
44
|
-
throw new Error(`Expected an E2E suite: ${Object.keys(e2eSuites).join(', ')}.`);
|
|
45
|
-
}
|
|
19
|
+
export const E2E_CONFIG_FILE = resolve(E2E_ROOT_DIR, 'e2e', 'config', 'playwright.config.ts');
|
|
20
|
+
export const E2E_SPECS_DIR = resolve(E2E_ROOT_DIR, 'e2e', 'specs');
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { createServer } from 'node:net';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
E2E_CONFIG_FILE,
|
|
7
|
+
E2E_LOCAL_BASE_URL,
|
|
8
|
+
E2E_PORT,
|
|
9
|
+
E2E_ROOT_DIR,
|
|
10
|
+
E2E_SPECS_DIR,
|
|
11
|
+
} from './e2e.ts';
|
|
12
|
+
import { combineGreps, parseE2eArgs } from './runE2eArgs.ts';
|
|
13
|
+
|
|
14
|
+
function run(command: string, args: string[]): void {
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.log(`\n===> ${[command, ...args].join(' ')}`);
|
|
17
|
+
execFileSync(command, args, {
|
|
18
|
+
cwd: E2E_ROOT_DIR,
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function assertPortAvailable(): Promise<void> {
|
|
24
|
+
const server = createServer();
|
|
25
|
+
await new Promise<void>((resolvePromise, reject) => {
|
|
26
|
+
server.once('error', reject);
|
|
27
|
+
server.listen(E2E_PORT, '127.0.0.1', () => {
|
|
28
|
+
server.close(error => {
|
|
29
|
+
if (error) reject(error);
|
|
30
|
+
else resolvePromise();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parsed = parseE2eArgs(process.argv.slice(2), E2E_SPECS_DIR);
|
|
37
|
+
if (parsed.mode === 'clean' && process.env.E2E_BASE_URL) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'The clean E2E run manages the local target and cannot be used with E2E_BASE_URL. Use test:e2e:fast for an external target.',
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (parsed.mode === 'clean') {
|
|
44
|
+
await assertPortAvailable();
|
|
45
|
+
run('npm', ['run', 'db:reset']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const playwrightCommand = resolve(
|
|
49
|
+
E2E_ROOT_DIR,
|
|
50
|
+
'node_modules',
|
|
51
|
+
'.bin',
|
|
52
|
+
process.platform === 'win32' ? 'playwright.cmd' : 'playwright',
|
|
53
|
+
);
|
|
54
|
+
const specPaths = parsed.specNames.map(name => resolve(E2E_SPECS_DIR, `${name}.spec.ts`));
|
|
55
|
+
const playwrightArgs = combineGreps(parsed.playwrightArgs, parsed.tags);
|
|
56
|
+
run(playwrightCommand, ['test', '--config', E2E_CONFIG_FILE, ...specPaths, ...playwrightArgs]);
|
|
57
|
+
|
|
58
|
+
// Keep this value referenced in the runner's startup output and make the local target explicit.
|
|
59
|
+
if (!process.env.E2E_BASE_URL) {
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.log(`E2E target: ${E2E_LOCAL_BASE_URL}`);
|
|
62
|
+
}
|