cabloy 5.1.122 → 5.1.124
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/.claude/skills/cabloy-frontend-scaffold/SKILL.md +13 -3
- package/.claude/skills/cabloy-workflow/SKILL.md +6 -0
- package/.claude/skills/cabloy-workflow/evals/evals.json +6 -0
- package/CHANGELOG.md +24 -0
- package/CLAUDE.md +1 -1
- package/cabloy-docs/.vitepress/config.mjs +4 -0
- package/cabloy-docs/frontend/form-layout-guide.md +29 -7
- package/cabloy-docs/fullstack/parallel-worktree-environment.md +87 -0
- package/e2e/scripts/e2e.ts +13 -1
- package/e2e/scripts/startE2eVona.ts +4 -1
- package/e2e/specs/a-commerce/commerce.spec.ts +129 -32
- package/package.json +2 -1
- package/playwright-report/data/24ac790ec112339c76cfb0c8e2c2539c2f953a0b.zip +0 -0
- package/playwright-report/data/2fddcabcdbd0b695061a565acfff243cb3a4f5e3.zip +0 -0
- package/playwright-report/data/4738038d9e861d63fd25937c9a3570934786a62f.zip +0 -0
- package/playwright-report/data/72f99c7da67c7f6ea53cab7d8681798b70d79a10.md +222 -0
- package/playwright-report/data/7b9684a7c793e9798b898f8ff50177b660a7a733.md +222 -0
- package/playwright-report/data/8b31595f5f1cc9d23863af51a592e6b90dcbc4ce.zip +0 -0
- package/playwright-report/data/95b578407a68806202dda917a61c50c750bd285b.md +222 -0
- package/playwright-report/data/ad960b3a58f224f5b4cad04a24067600064ffb7b.md +222 -0
- package/playwright-report/index.html +90 -0
- package/playwright-report/trace/assets/codeMirrorModule-LEHpjmcn.js +32 -0
- package/playwright-report/trace/assets/defaultSettingsView-BNmKHKpQ.js +264 -0
- package/playwright-report/trace/assets/urlMatch-BYQrIQwR.js +1 -0
- package/playwright-report/trace/codeMirrorModule.DYBRYzYX.css +1 -0
- package/playwright-report/trace/codicon.DCmgc-ay.ttf +0 -0
- package/playwright-report/trace/defaultSettingsView.CjdS-WJx.css +1 -0
- package/playwright-report/trace/index.CzXZzn5A.css +1 -0
- package/playwright-report/trace/index.DMMX1gXU.js +2 -0
- package/playwright-report/trace/index.html +44 -0
- package/playwright-report/trace/manifest.webmanifest +16 -0
- package/playwright-report/trace/playwright-logo.svg +9 -0
- package/playwright-report/trace/snapshot.html +10 -0
- package/playwright-report/trace/snapshot.v8KI4P3m.js +2 -0
- package/playwright-report/trace/sw.bundle.js +5 -0
- package/playwright-report/trace/uiMode.BZQ54Kgt.css +1 -0
- package/playwright-report/trace/uiMode.Ut8wwJNp.js +6 -0
- package/playwright-report/trace/uiMode.html +18 -0
- package/playwright-report/trace/xtermModule.DYP7pi_n.css +32 -0
- package/vona/env/.env +12 -0
- package/vona/env/.env.test +7 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}Create.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}Update.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/dto/{{resourceName}}View.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/3-en-us.ts +6 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/snippets/4-zh-cn.ts +6 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}Create.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}Update.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/dto/{{resourceName}}View.tsx_ +19 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/3-en-us.ts +6 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/snippets/4-zh-cn.ts +6 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/pnpm-lock.yaml +255 -12
- package/vona/pnpm-workspace.yaml +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +45 -3
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +25 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/payScene.commerceOrder.ts +20 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAttempt.tsx +9 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAudit.tsx +5 -2
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/refundAttempt.tsx +6 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/index.ts +2 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/lib/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/commercePayScene.ts +54 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +7 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/types/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +21 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +30 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/en-us.ts +4 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/zh-cn.ts +4 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateCreate.tsx +88 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateUpdate.tsx +44 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateView.tsx +72 -1
- package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponTemplateFormLayout.test.ts +121 -0
- package/vona/src/suite/a-commerce/modules/commerce-siteweb/src/bean/ssrMenu.home.ts +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +0 -21
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutResult.tsx +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +79 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +227 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +121 -13
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/refundLifecycle.test.ts +312 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/package.json +76 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +842 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payProvider.ts +92 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payScene.ts +105 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.index.ts +28 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.redlock.ts +11 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/meta.version.ts +113 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/queue.outboxDispatch.ts +31 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/schedule.outboxDispatch.ts +11 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/paymentSession.ts +55 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/controller/webhook.ts +47 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/paymentSessionStart.tsx +8 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/paymentSessionView.tsx +69 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/dto/webhookReceipt.tsx +12 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/outboxEvent.tsx +46 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/paymentAudit.tsx +36 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/paymentSession.tsx +72 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/providerOperation.tsx +54 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/refundOperation.tsx +37 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/entity/webhookInbox.tsx +59 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/payProvider.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/payScene.ts +7 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/outboxEvent.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentAudit.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/providerOperation.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/refundOperation.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/model/webhookInbox.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/outbox.ts +156 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentSession.ts +278 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/providerOperation.ts +5 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/service/webhook.ts +127 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payProvider.ts +74 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payScene.ts +63 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +151 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/test/outbox.test.ts +209 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/test/paymentSession.test.ts +153 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/test/webhook.test.ts +278 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/a-pay/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/package.json +55 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/index.ts +182 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/bean/payProvider.mock.ts +149 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/controller/mockPayment.ts +25 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockPaymentComplete.tsx +13 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/dto/mockPaymentReceipt.tsx +16 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/service/payMock.ts +78 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/src/types/index.ts +10 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/test/payMock.test.ts +141 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/package.json +55 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/index.ts +62 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/bean/payProvider.paypal.ts +91 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +3 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/src/types/index.ts +9 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/package.json +55 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/index.ts +62 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/bean/payProvider.stripe.ts +88 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +2 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/src/types/index.ts +8 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-pay/package.json +15 -0
- package/vona/src/suite-vendor/a-pay/tsconfig.base.json +4 -0
- package/vona/src/suite-vendor/a-pay/tsconfig.json +19 -0
- package/zova/env/.env +1 -0
- package/zova/openapi.config.ts +5 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/project/openapi.config.ts +5 -1
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +8 -0
- package/zova/packages-cli/cli-set-front/src/lib/common/cliCreatePage.ts +7 -24
- package/zova/packages-cli/cli-set-front/src/lib/common/utils.ts +10 -1
- 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/types/interface/component.ts +5 -0
- package/zova/pnpm-lock.yaml +96 -26
- package/zova/src/front/config/config/config.cabloyCommerce.ts +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +110 -12
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +1292 -160
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +0 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +1 -47
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +23 -8
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +249 -62
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +5 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +152 -39
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +1 -1
- package/zova/src/suite-vendor/a-pay/modules/a-pay/cli/openapi.config.ts +9 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/package.json +53 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/component/paymentNextAction.ts +31 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/index.ts +217 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/baseURL.ts +5 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/index.ts +3 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/schemas.ts +584 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/openapi/types.ts +9324 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/api/paymentSession.ts +52 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/apiSchema/paymentSession.ts +20 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/component/paymentNextAction/controller.tsx +76 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/index.ts +3 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/model/paymentSession.ts +32 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/service/paymentCoordinator.ts +13 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/types/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/src/types/payment.ts +25 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/a-pay/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/cli/openapi.config.ts +9 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/package.json +53 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/index.ts +140 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/baseURL.ts +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/index.ts +3 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/schemas.ts +584 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/openapi/types.ts +9324 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/api/payMockPayment.ts +34 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/apiSchema/payMockPayment.ts +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/src/model/payMockPayment.ts +20 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-mock/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/package.json +52 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/index.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/src/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-paypal/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/package.json +52 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/index.ts +26 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/.metadata/this.ts +2 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/src/index.ts +1 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.build.json +13 -0
- package/zova/src/suite-vendor/a-pay/modules/pay-stripe/tsconfig.json +5 -0
- package/zova/src/suite-vendor/a-pay/package.json +15 -0
- package/zova/src/suite-vendor/a-pay/tsconfig.base.json +4 -0
- package/zova/src/suite-vendor/a-pay/tsconfig.json +4 -0
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/payment.ts +0 -23
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradePayment.ts +0 -36
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradePayment.ts +0 -13
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/payment.ts +0 -25
- /package/vona/patches/{zova-core@5.1.79.patch → zova-core@5.1.81.patch} +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Instructions
|
|
2
|
+
|
|
3
|
+
- Following Playwright test failed.
|
|
4
|
+
- Explain why, be concise, respect Playwright best practices.
|
|
5
|
+
- Provide a snippet of code with the fix, if possible.
|
|
6
|
+
|
|
7
|
+
# Test info
|
|
8
|
+
|
|
9
|
+
- Name: commerce.spec.ts >> Phase 50/60: authenticated customer completes payment and observes operator shipment
|
|
10
|
+
- Location: e2e/specs/a-commerce/commerce.spec.ts:348:1
|
|
11
|
+
|
|
12
|
+
# Error details
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Error: expect(received).toEqual(expected) // deep equality
|
|
16
|
+
|
|
17
|
+
Expected: Any<String>
|
|
18
|
+
Received: undefined
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
# Test source
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
1 | import type { APIRequestContext, Browser, Locator, Page, TestInfo } from '@playwright/test';
|
|
25
|
+
2 |
|
|
26
|
+
3 | import { expect, test } from '@playwright/test';
|
|
27
|
+
4 |
|
|
28
|
+
5 | const privateMarkers = ['cart', 'address', 'order', 'coupon', 'payment'];
|
|
29
|
+
6 | const addressResourceUrl =
|
|
30
|
+
7 | /\/commerce-admin\/rest\/resource\/commerce-member(?:%3A|:|%253A)address(?:[/?#]|$)/;
|
|
31
|
+
8 | const addressMinePath = '/api/commerce/member/address/mine';
|
|
32
|
+
9 | const addressActionPath = '/api/commerce/member/address';
|
|
33
|
+
10 | const passportTestActivateCurrentPath = '/api/home/user/passportTest/activateCurrent';
|
|
34
|
+
11 |
|
|
35
|
+
12 | interface IAddressFixture {
|
|
36
|
+
13 | addressLine1: string;
|
|
37
|
+
14 | city: string;
|
|
38
|
+
15 | recipientName: string;
|
|
39
|
+
16 | updatedCity: string;
|
|
40
|
+
17 | }
|
|
41
|
+
18 |
|
|
42
|
+
19 | function collectPageErrors(page: Page) {
|
|
43
|
+
20 | const errors: Error[] = [];
|
|
44
|
+
21 | page.on('pageerror', error => {
|
|
45
|
+
22 | errors.push(error);
|
|
46
|
+
23 | });
|
|
47
|
+
24 | return errors;
|
|
48
|
+
25 | }
|
|
49
|
+
26 |
|
|
50
|
+
27 | function waitForApiResponse(page: Page, method: string, path: string | RegExp) {
|
|
51
|
+
28 | return page.waitForResponse(response => {
|
|
52
|
+
29 | const url = new URL(response.url());
|
|
53
|
+
30 | return (
|
|
54
|
+
31 | response.request().method() === method &&
|
|
55
|
+
32 | (typeof path === 'string' ? url.pathname === path : path.test(url.pathname)) &&
|
|
56
|
+
33 | !response.request().headers()['x-vona-openapi-schema']
|
|
57
|
+
34 | );
|
|
58
|
+
35 | });
|
|
59
|
+
36 | }
|
|
60
|
+
37 |
|
|
61
|
+
38 | function waitForAddressResponse(page: Page, method: string, path: string | RegExp) {
|
|
62
|
+
39 | return waitForApiResponse(page, method, path);
|
|
63
|
+
40 | }
|
|
64
|
+
41 |
|
|
65
|
+
42 | function waitForAddressMine(page: Page) {
|
|
66
|
+
43 | return waitForAddressResponse(page, 'GET', addressMinePath);
|
|
67
|
+
44 | }
|
|
68
|
+
45 |
|
|
69
|
+
46 | async function getAdminOrderRow(page: Page, orderId: number): Promise<Locator> {
|
|
70
|
+
47 | const row = page.locator(`tr:has(td:first-child:text-is("${orderId}"))`);
|
|
71
|
+
48 | await expect(row).toHaveCount(1);
|
|
72
|
+
49 | return row;
|
|
73
|
+
50 | }
|
|
74
|
+
51 |
|
|
75
|
+
52 | async function login(
|
|
76
|
+
53 | page: Page,
|
|
77
|
+
54 | path: string,
|
|
78
|
+
55 | username: string,
|
|
79
|
+
56 | password: string,
|
|
80
|
+
57 | hydratedSite: string,
|
|
81
|
+
58 | ) {
|
|
82
|
+
59 | await page.goto(path, { waitUntil: 'load' });
|
|
83
|
+
60 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', hydratedSite);
|
|
84
|
+
61 | if (!page.url().includes('/login')) {
|
|
85
|
+
62 | await page.waitForURL(/\/login(?:\?|$)/);
|
|
86
|
+
63 | }
|
|
87
|
+
64 | if (page.url().includes('/login')) {
|
|
88
|
+
65 | const usernameInput = page.getByPlaceholder('Your Username');
|
|
89
|
+
66 | const passwordInput = page.getByPlaceholder('Your Password');
|
|
90
|
+
67 | await usernameInput.fill(username);
|
|
91
|
+
68 | await passwordInput.fill(password);
|
|
92
|
+
69 | await expect(usernameInput).toHaveValue(username);
|
|
93
|
+
70 | await expect(passwordInput).toHaveValue(password);
|
|
94
|
+
71 | await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
|
|
95
|
+
72 | const loginResponse = waitForApiResponse(page, 'POST', '/api/home/user/passport/login');
|
|
96
|
+
73 | await page.getByRole('button', { name: 'Login', exact: true }).click();
|
|
97
|
+
74 | const loginResponseValue = await loginResponse;
|
|
98
|
+
75 | if (!loginResponseValue.ok()) {
|
|
99
|
+
76 | throw new Error(
|
|
100
|
+
77 | `login failed: ${loginResponseValue.status()} ${await loginResponseValue.text()}`,
|
|
101
|
+
78 | );
|
|
102
|
+
79 | }
|
|
103
|
+
80 | await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
|
104
|
+
81 | }
|
|
105
|
+
82 | }
|
|
106
|
+
83 |
|
|
107
|
+
84 | async function registerCustomer(
|
|
108
|
+
85 | request: APIRequestContext,
|
|
109
|
+
86 | testInfo: TestInfo,
|
|
110
|
+
87 | ): Promise<{ password: string; username: string }> {
|
|
111
|
+
88 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
112
|
+
89 | const username = `e2e-address-${id}`;
|
|
113
|
+
90 | const password = 'address-e2e-password';
|
|
114
|
+
91 | const captchaResponse = await request.post('/api/captcha/create', {
|
|
115
|
+
92 | data: { scene: 'captcha-simple:simple' },
|
|
116
|
+
93 | });
|
|
117
|
+
94 | expect(captchaResponse.ok()).toBeTruthy();
|
|
118
|
+
95 | const captcha = (await captchaResponse.json()).data;
|
|
119
|
+
96 | expect(captcha?.id).toEqual(expect.any(String));
|
|
120
|
+
> 97 | expect(captcha?.token).toEqual(expect.any(String));
|
|
121
|
+
| ^ Error: expect(received).toEqual(expected) // deep equality
|
|
122
|
+
98 |
|
|
123
|
+
99 | const registerResponse = await request.post('/api/home/user/passport/register', {
|
|
124
|
+
100 | data: {
|
|
125
|
+
101 | username,
|
|
126
|
+
102 | email: `${username}@example.test`,
|
|
127
|
+
103 | password,
|
|
128
|
+
104 | passwordConfirm: password,
|
|
129
|
+
105 | captcha: { id: captcha.id, token: captcha.token },
|
|
130
|
+
106 | },
|
|
131
|
+
107 | });
|
|
132
|
+
108 | expect(registerResponse.ok()).toBeTruthy();
|
|
133
|
+
109 | const registration = (await registerResponse.json()).data;
|
|
134
|
+
110 | expect(registration?.jwt?.accessToken).toEqual(expect.any(String));
|
|
135
|
+
111 | const activateResponse = await request.post(passportTestActivateCurrentPath, {
|
|
136
|
+
112 | headers: { Authorization: `Bearer ${registration.jwt.accessToken}` },
|
|
137
|
+
113 | });
|
|
138
|
+
114 | expect(activateResponse.ok()).toBeTruthy();
|
|
139
|
+
115 | return { username, password };
|
|
140
|
+
116 | }
|
|
141
|
+
117 |
|
|
142
|
+
118 | function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
143
|
+
119 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
144
|
+
120 | return {
|
|
145
|
+
121 | recipientName: `E2E Address ${id}`,
|
|
146
|
+
122 | addressLine1: `1 Test Street ${id}`,
|
|
147
|
+
123 | city: `Original City ${id}`,
|
|
148
|
+
124 | updatedCity: `Updated City ${id}`,
|
|
149
|
+
125 | };
|
|
150
|
+
126 | }
|
|
151
|
+
127 |
|
|
152
|
+
128 | async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
153
|
+
129 | await page.getByPlaceholder('Recipient name').fill(fixture.recipientName);
|
|
154
|
+
130 | await page.getByPlaceholder('Phone').fill('15555550123');
|
|
155
|
+
131 | await page.getByPlaceholder('Country code').fill('US');
|
|
156
|
+
132 | await page.getByPlaceholder('Region').fill('California');
|
|
157
|
+
133 | await page.getByPlaceholder('City').fill(fixture.city);
|
|
158
|
+
134 | await page.getByPlaceholder('Postal code').fill('94105');
|
|
159
|
+
135 | await page.getByPlaceholder('Address line 1').fill(fixture.addressLine1);
|
|
160
|
+
136 | await page.getByPlaceholder('Address line 2').fill('Suite E2E');
|
|
161
|
+
137 | }
|
|
162
|
+
138 |
|
|
163
|
+
139 | async function createAddressThroughCustomerPage(
|
|
164
|
+
140 | browser: Browser,
|
|
165
|
+
141 | request: APIRequestContext,
|
|
166
|
+
142 | testInfo: TestInfo,
|
|
167
|
+
143 | ) {
|
|
168
|
+
144 | const customer = await registerCustomer(request, testInfo);
|
|
169
|
+
145 | const fixture = createAddressFixture(testInfo);
|
|
170
|
+
146 | const context = await browser.newContext();
|
|
171
|
+
147 | const page = await context.newPage();
|
|
172
|
+
148 | const pageErrors = collectPageErrors(page);
|
|
173
|
+
149 |
|
|
174
|
+
150 | await login(page, '/commerce/address', customer.username, customer.password, 'commerce');
|
|
175
|
+
151 | await expect(page).toHaveURL(/\/commerce\/address(?:\?|$)/);
|
|
176
|
+
152 | await expect(page.getByRole('heading', { name: 'Addresses' })).toBeVisible();
|
|
177
|
+
153 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
178
|
+
154 |
|
|
179
|
+
155 | await fillAddressForm(page, fixture);
|
|
180
|
+
156 | const createResponse = waitForAddressResponse(page, 'POST', `${addressActionPath}/createMine`);
|
|
181
|
+
157 | const mineResponse = waitForAddressMine(page);
|
|
182
|
+
158 | await page.getByRole('button', { name: 'Save address', exact: true }).click();
|
|
183
|
+
159 | await createResponse;
|
|
184
|
+
160 | await mineResponse;
|
|
185
|
+
161 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toBeVisible();
|
|
186
|
+
162 |
|
|
187
|
+
163 | return { context, fixture, page, pageErrors };
|
|
188
|
+
164 | }
|
|
189
|
+
165 |
|
|
190
|
+
166 | async function deleteAddressThroughCustomerPage(page: Page, fixture: IAddressFixture) {
|
|
191
|
+
167 | const card = page
|
|
192
|
+
168 | .locator('article')
|
|
193
|
+
169 | .filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
194
|
+
170 | const deleteResponse = waitForAddressResponse(page, 'DELETE', /\/deleteMine\/[^/]+$/);
|
|
195
|
+
171 | const mineResponse = waitForAddressMine(page);
|
|
196
|
+
172 | await card.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
197
|
+
173 | await deleteResponse;
|
|
198
|
+
174 | await mineResponse;
|
|
199
|
+
175 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toHaveCount(0);
|
|
200
|
+
176 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
201
|
+
177 | }
|
|
202
|
+
178 |
|
|
203
|
+
179 | test(
|
|
204
|
+
180 | 'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
|
|
205
|
+
181 | { tag: ['@web', '@smoke'] },
|
|
206
|
+
182 | async ({ page, request }) => {
|
|
207
|
+
183 | const response = await request.get('/commerce');
|
|
208
|
+
184 | expect(response.ok()).toBeTruthy();
|
|
209
|
+
185 | const html = await response.text();
|
|
210
|
+
186 | const normalizedHtml = html.toLowerCase();
|
|
211
|
+
187 | expect(normalizedHtml).not.toContain('data-zova-hydrated');
|
|
212
|
+
188 |
|
|
213
|
+
189 | for (const marker of privateMarkers) {
|
|
214
|
+
190 | expect(normalizedHtml).not.toContain(`"${marker}"`);
|
|
215
|
+
191 | }
|
|
216
|
+
192 |
|
|
217
|
+
193 | const pageErrors = collectPageErrors(page);
|
|
218
|
+
194 | const documentResponse = await page.goto('/commerce', { waitUntil: 'load' });
|
|
219
|
+
195 | expect(documentResponse?.ok()).toBeTruthy();
|
|
220
|
+
196 | await expect(page).toHaveURL(/\/commerce(?:\/|$)/);
|
|
221
|
+
197 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
222
|
+
```
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Instructions
|
|
2
|
+
|
|
3
|
+
- Following Playwright test failed.
|
|
4
|
+
- Explain why, be concise, respect Playwright best practices.
|
|
5
|
+
- Provide a snippet of code with the fix, if possible.
|
|
6
|
+
|
|
7
|
+
# Test info
|
|
8
|
+
|
|
9
|
+
- Name: commerce.spec.ts >> ATP-ADDR-01: systemAdmin inspects Address Resource without mutation controls
|
|
10
|
+
- Location: e2e/specs/a-commerce/commerce.spec.ts:652:1
|
|
11
|
+
|
|
12
|
+
# Error details
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Error: expect(received).toEqual(expected) // deep equality
|
|
16
|
+
|
|
17
|
+
Expected: Any<String>
|
|
18
|
+
Received: undefined
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
# Test source
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
1 | import type { APIRequestContext, Browser, Locator, Page, TestInfo } from '@playwright/test';
|
|
25
|
+
2 |
|
|
26
|
+
3 | import { expect, test } from '@playwright/test';
|
|
27
|
+
4 |
|
|
28
|
+
5 | const privateMarkers = ['cart', 'address', 'order', 'coupon', 'payment'];
|
|
29
|
+
6 | const addressResourceUrl =
|
|
30
|
+
7 | /\/commerce-admin\/rest\/resource\/commerce-member(?:%3A|:|%253A)address(?:[/?#]|$)/;
|
|
31
|
+
8 | const addressMinePath = '/api/commerce/member/address/mine';
|
|
32
|
+
9 | const addressActionPath = '/api/commerce/member/address';
|
|
33
|
+
10 | const passportTestActivateCurrentPath = '/api/home/user/passportTest/activateCurrent';
|
|
34
|
+
11 |
|
|
35
|
+
12 | interface IAddressFixture {
|
|
36
|
+
13 | addressLine1: string;
|
|
37
|
+
14 | city: string;
|
|
38
|
+
15 | recipientName: string;
|
|
39
|
+
16 | updatedCity: string;
|
|
40
|
+
17 | }
|
|
41
|
+
18 |
|
|
42
|
+
19 | function collectPageErrors(page: Page) {
|
|
43
|
+
20 | const errors: Error[] = [];
|
|
44
|
+
21 | page.on('pageerror', error => {
|
|
45
|
+
22 | errors.push(error);
|
|
46
|
+
23 | });
|
|
47
|
+
24 | return errors;
|
|
48
|
+
25 | }
|
|
49
|
+
26 |
|
|
50
|
+
27 | function waitForApiResponse(page: Page, method: string, path: string | RegExp) {
|
|
51
|
+
28 | return page.waitForResponse(response => {
|
|
52
|
+
29 | const url = new URL(response.url());
|
|
53
|
+
30 | return (
|
|
54
|
+
31 | response.request().method() === method &&
|
|
55
|
+
32 | (typeof path === 'string' ? url.pathname === path : path.test(url.pathname)) &&
|
|
56
|
+
33 | !response.request().headers()['x-vona-openapi-schema']
|
|
57
|
+
34 | );
|
|
58
|
+
35 | });
|
|
59
|
+
36 | }
|
|
60
|
+
37 |
|
|
61
|
+
38 | function waitForAddressResponse(page: Page, method: string, path: string | RegExp) {
|
|
62
|
+
39 | return waitForApiResponse(page, method, path);
|
|
63
|
+
40 | }
|
|
64
|
+
41 |
|
|
65
|
+
42 | function waitForAddressMine(page: Page) {
|
|
66
|
+
43 | return waitForAddressResponse(page, 'GET', addressMinePath);
|
|
67
|
+
44 | }
|
|
68
|
+
45 |
|
|
69
|
+
46 | async function getAdminOrderRow(page: Page, orderId: number): Promise<Locator> {
|
|
70
|
+
47 | const row = page.locator(`tr:has(td:first-child:text-is("${orderId}"))`);
|
|
71
|
+
48 | await expect(row).toHaveCount(1);
|
|
72
|
+
49 | return row;
|
|
73
|
+
50 | }
|
|
74
|
+
51 |
|
|
75
|
+
52 | async function login(
|
|
76
|
+
53 | page: Page,
|
|
77
|
+
54 | path: string,
|
|
78
|
+
55 | username: string,
|
|
79
|
+
56 | password: string,
|
|
80
|
+
57 | hydratedSite: string,
|
|
81
|
+
58 | ) {
|
|
82
|
+
59 | await page.goto(path, { waitUntil: 'load' });
|
|
83
|
+
60 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', hydratedSite);
|
|
84
|
+
61 | if (!page.url().includes('/login')) {
|
|
85
|
+
62 | await page.waitForURL(/\/login(?:\?|$)/);
|
|
86
|
+
63 | }
|
|
87
|
+
64 | if (page.url().includes('/login')) {
|
|
88
|
+
65 | const usernameInput = page.getByPlaceholder('Your Username');
|
|
89
|
+
66 | const passwordInput = page.getByPlaceholder('Your Password');
|
|
90
|
+
67 | await usernameInput.fill(username);
|
|
91
|
+
68 | await passwordInput.fill(password);
|
|
92
|
+
69 | await expect(usernameInput).toHaveValue(username);
|
|
93
|
+
70 | await expect(passwordInput).toHaveValue(password);
|
|
94
|
+
71 | await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
|
|
95
|
+
72 | const loginResponse = waitForApiResponse(page, 'POST', '/api/home/user/passport/login');
|
|
96
|
+
73 | await page.getByRole('button', { name: 'Login', exact: true }).click();
|
|
97
|
+
74 | const loginResponseValue = await loginResponse;
|
|
98
|
+
75 | if (!loginResponseValue.ok()) {
|
|
99
|
+
76 | throw new Error(
|
|
100
|
+
77 | `login failed: ${loginResponseValue.status()} ${await loginResponseValue.text()}`,
|
|
101
|
+
78 | );
|
|
102
|
+
79 | }
|
|
103
|
+
80 | await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
|
104
|
+
81 | }
|
|
105
|
+
82 | }
|
|
106
|
+
83 |
|
|
107
|
+
84 | async function registerCustomer(
|
|
108
|
+
85 | request: APIRequestContext,
|
|
109
|
+
86 | testInfo: TestInfo,
|
|
110
|
+
87 | ): Promise<{ password: string; username: string }> {
|
|
111
|
+
88 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
112
|
+
89 | const username = `e2e-address-${id}`;
|
|
113
|
+
90 | const password = 'address-e2e-password';
|
|
114
|
+
91 | const captchaResponse = await request.post('/api/captcha/create', {
|
|
115
|
+
92 | data: { scene: 'captcha-simple:simple' },
|
|
116
|
+
93 | });
|
|
117
|
+
94 | expect(captchaResponse.ok()).toBeTruthy();
|
|
118
|
+
95 | const captcha = (await captchaResponse.json()).data;
|
|
119
|
+
96 | expect(captcha?.id).toEqual(expect.any(String));
|
|
120
|
+
> 97 | expect(captcha?.token).toEqual(expect.any(String));
|
|
121
|
+
| ^ Error: expect(received).toEqual(expected) // deep equality
|
|
122
|
+
98 |
|
|
123
|
+
99 | const registerResponse = await request.post('/api/home/user/passport/register', {
|
|
124
|
+
100 | data: {
|
|
125
|
+
101 | username,
|
|
126
|
+
102 | email: `${username}@example.test`,
|
|
127
|
+
103 | password,
|
|
128
|
+
104 | passwordConfirm: password,
|
|
129
|
+
105 | captcha: { id: captcha.id, token: captcha.token },
|
|
130
|
+
106 | },
|
|
131
|
+
107 | });
|
|
132
|
+
108 | expect(registerResponse.ok()).toBeTruthy();
|
|
133
|
+
109 | const registration = (await registerResponse.json()).data;
|
|
134
|
+
110 | expect(registration?.jwt?.accessToken).toEqual(expect.any(String));
|
|
135
|
+
111 | const activateResponse = await request.post(passportTestActivateCurrentPath, {
|
|
136
|
+
112 | headers: { Authorization: `Bearer ${registration.jwt.accessToken}` },
|
|
137
|
+
113 | });
|
|
138
|
+
114 | expect(activateResponse.ok()).toBeTruthy();
|
|
139
|
+
115 | return { username, password };
|
|
140
|
+
116 | }
|
|
141
|
+
117 |
|
|
142
|
+
118 | function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
143
|
+
119 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
144
|
+
120 | return {
|
|
145
|
+
121 | recipientName: `E2E Address ${id}`,
|
|
146
|
+
122 | addressLine1: `1 Test Street ${id}`,
|
|
147
|
+
123 | city: `Original City ${id}`,
|
|
148
|
+
124 | updatedCity: `Updated City ${id}`,
|
|
149
|
+
125 | };
|
|
150
|
+
126 | }
|
|
151
|
+
127 |
|
|
152
|
+
128 | async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
153
|
+
129 | await page.getByPlaceholder('Recipient name').fill(fixture.recipientName);
|
|
154
|
+
130 | await page.getByPlaceholder('Phone').fill('15555550123');
|
|
155
|
+
131 | await page.getByPlaceholder('Country code').fill('US');
|
|
156
|
+
132 | await page.getByPlaceholder('Region').fill('California');
|
|
157
|
+
133 | await page.getByPlaceholder('City').fill(fixture.city);
|
|
158
|
+
134 | await page.getByPlaceholder('Postal code').fill('94105');
|
|
159
|
+
135 | await page.getByPlaceholder('Address line 1').fill(fixture.addressLine1);
|
|
160
|
+
136 | await page.getByPlaceholder('Address line 2').fill('Suite E2E');
|
|
161
|
+
137 | }
|
|
162
|
+
138 |
|
|
163
|
+
139 | async function createAddressThroughCustomerPage(
|
|
164
|
+
140 | browser: Browser,
|
|
165
|
+
141 | request: APIRequestContext,
|
|
166
|
+
142 | testInfo: TestInfo,
|
|
167
|
+
143 | ) {
|
|
168
|
+
144 | const customer = await registerCustomer(request, testInfo);
|
|
169
|
+
145 | const fixture = createAddressFixture(testInfo);
|
|
170
|
+
146 | const context = await browser.newContext();
|
|
171
|
+
147 | const page = await context.newPage();
|
|
172
|
+
148 | const pageErrors = collectPageErrors(page);
|
|
173
|
+
149 |
|
|
174
|
+
150 | await login(page, '/commerce/address', customer.username, customer.password, 'commerce');
|
|
175
|
+
151 | await expect(page).toHaveURL(/\/commerce\/address(?:\?|$)/);
|
|
176
|
+
152 | await expect(page.getByRole('heading', { name: 'Addresses' })).toBeVisible();
|
|
177
|
+
153 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
178
|
+
154 |
|
|
179
|
+
155 | await fillAddressForm(page, fixture);
|
|
180
|
+
156 | const createResponse = waitForAddressResponse(page, 'POST', `${addressActionPath}/createMine`);
|
|
181
|
+
157 | const mineResponse = waitForAddressMine(page);
|
|
182
|
+
158 | await page.getByRole('button', { name: 'Save address', exact: true }).click();
|
|
183
|
+
159 | await createResponse;
|
|
184
|
+
160 | await mineResponse;
|
|
185
|
+
161 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toBeVisible();
|
|
186
|
+
162 |
|
|
187
|
+
163 | return { context, fixture, page, pageErrors };
|
|
188
|
+
164 | }
|
|
189
|
+
165 |
|
|
190
|
+
166 | async function deleteAddressThroughCustomerPage(page: Page, fixture: IAddressFixture) {
|
|
191
|
+
167 | const card = page
|
|
192
|
+
168 | .locator('article')
|
|
193
|
+
169 | .filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
194
|
+
170 | const deleteResponse = waitForAddressResponse(page, 'DELETE', /\/deleteMine\/[^/]+$/);
|
|
195
|
+
171 | const mineResponse = waitForAddressMine(page);
|
|
196
|
+
172 | await card.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
197
|
+
173 | await deleteResponse;
|
|
198
|
+
174 | await mineResponse;
|
|
199
|
+
175 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toHaveCount(0);
|
|
200
|
+
176 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
201
|
+
177 | }
|
|
202
|
+
178 |
|
|
203
|
+
179 | test(
|
|
204
|
+
180 | 'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
|
|
205
|
+
181 | { tag: ['@web', '@smoke'] },
|
|
206
|
+
182 | async ({ page, request }) => {
|
|
207
|
+
183 | const response = await request.get('/commerce');
|
|
208
|
+
184 | expect(response.ok()).toBeTruthy();
|
|
209
|
+
185 | const html = await response.text();
|
|
210
|
+
186 | const normalizedHtml = html.toLowerCase();
|
|
211
|
+
187 | expect(normalizedHtml).not.toContain('data-zova-hydrated');
|
|
212
|
+
188 |
|
|
213
|
+
189 | for (const marker of privateMarkers) {
|
|
214
|
+
190 | expect(normalizedHtml).not.toContain(`"${marker}"`);
|
|
215
|
+
191 | }
|
|
216
|
+
192 |
|
|
217
|
+
193 | const pageErrors = collectPageErrors(page);
|
|
218
|
+
194 | const documentResponse = await page.goto('/commerce', { waitUntil: 'load' });
|
|
219
|
+
195 | expect(documentResponse?.ok()).toBeTruthy();
|
|
220
|
+
196 | await expect(page).toHaveURL(/\/commerce(?:\/|$)/);
|
|
221
|
+
197 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
222
|
+
```
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Instructions
|
|
2
|
+
|
|
3
|
+
- Following Playwright test failed.
|
|
4
|
+
- Explain why, be concise, respect Playwright best practices.
|
|
5
|
+
- Provide a snippet of code with the fix, if possible.
|
|
6
|
+
|
|
7
|
+
# Test info
|
|
8
|
+
|
|
9
|
+
- Name: commerce.spec.ts >> Phase 60: customer requests and operator executes a whole-order refund
|
|
10
|
+
- Location: e2e/specs/a-commerce/commerce.spec.ts:539:1
|
|
11
|
+
|
|
12
|
+
# Error details
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Error: expect(received).toEqual(expected) // deep equality
|
|
16
|
+
|
|
17
|
+
Expected: Any<String>
|
|
18
|
+
Received: undefined
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
# Test source
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
1 | import type { APIRequestContext, Browser, Locator, Page, TestInfo } from '@playwright/test';
|
|
25
|
+
2 |
|
|
26
|
+
3 | import { expect, test } from '@playwright/test';
|
|
27
|
+
4 |
|
|
28
|
+
5 | const privateMarkers = ['cart', 'address', 'order', 'coupon', 'payment'];
|
|
29
|
+
6 | const addressResourceUrl =
|
|
30
|
+
7 | /\/commerce-admin\/rest\/resource\/commerce-member(?:%3A|:|%253A)address(?:[/?#]|$)/;
|
|
31
|
+
8 | const addressMinePath = '/api/commerce/member/address/mine';
|
|
32
|
+
9 | const addressActionPath = '/api/commerce/member/address';
|
|
33
|
+
10 | const passportTestActivateCurrentPath = '/api/home/user/passportTest/activateCurrent';
|
|
34
|
+
11 |
|
|
35
|
+
12 | interface IAddressFixture {
|
|
36
|
+
13 | addressLine1: string;
|
|
37
|
+
14 | city: string;
|
|
38
|
+
15 | recipientName: string;
|
|
39
|
+
16 | updatedCity: string;
|
|
40
|
+
17 | }
|
|
41
|
+
18 |
|
|
42
|
+
19 | function collectPageErrors(page: Page) {
|
|
43
|
+
20 | const errors: Error[] = [];
|
|
44
|
+
21 | page.on('pageerror', error => {
|
|
45
|
+
22 | errors.push(error);
|
|
46
|
+
23 | });
|
|
47
|
+
24 | return errors;
|
|
48
|
+
25 | }
|
|
49
|
+
26 |
|
|
50
|
+
27 | function waitForApiResponse(page: Page, method: string, path: string | RegExp) {
|
|
51
|
+
28 | return page.waitForResponse(response => {
|
|
52
|
+
29 | const url = new URL(response.url());
|
|
53
|
+
30 | return (
|
|
54
|
+
31 | response.request().method() === method &&
|
|
55
|
+
32 | (typeof path === 'string' ? url.pathname === path : path.test(url.pathname)) &&
|
|
56
|
+
33 | !response.request().headers()['x-vona-openapi-schema']
|
|
57
|
+
34 | );
|
|
58
|
+
35 | });
|
|
59
|
+
36 | }
|
|
60
|
+
37 |
|
|
61
|
+
38 | function waitForAddressResponse(page: Page, method: string, path: string | RegExp) {
|
|
62
|
+
39 | return waitForApiResponse(page, method, path);
|
|
63
|
+
40 | }
|
|
64
|
+
41 |
|
|
65
|
+
42 | function waitForAddressMine(page: Page) {
|
|
66
|
+
43 | return waitForAddressResponse(page, 'GET', addressMinePath);
|
|
67
|
+
44 | }
|
|
68
|
+
45 |
|
|
69
|
+
46 | async function getAdminOrderRow(page: Page, orderId: number): Promise<Locator> {
|
|
70
|
+
47 | const row = page.locator(`tr:has(td:first-child:text-is("${orderId}"))`);
|
|
71
|
+
48 | await expect(row).toHaveCount(1);
|
|
72
|
+
49 | return row;
|
|
73
|
+
50 | }
|
|
74
|
+
51 |
|
|
75
|
+
52 | async function login(
|
|
76
|
+
53 | page: Page,
|
|
77
|
+
54 | path: string,
|
|
78
|
+
55 | username: string,
|
|
79
|
+
56 | password: string,
|
|
80
|
+
57 | hydratedSite: string,
|
|
81
|
+
58 | ) {
|
|
82
|
+
59 | await page.goto(path, { waitUntil: 'load' });
|
|
83
|
+
60 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', hydratedSite);
|
|
84
|
+
61 | if (!page.url().includes('/login')) {
|
|
85
|
+
62 | await page.waitForURL(/\/login(?:\?|$)/);
|
|
86
|
+
63 | }
|
|
87
|
+
64 | if (page.url().includes('/login')) {
|
|
88
|
+
65 | const usernameInput = page.getByPlaceholder('Your Username');
|
|
89
|
+
66 | const passwordInput = page.getByPlaceholder('Your Password');
|
|
90
|
+
67 | await usernameInput.fill(username);
|
|
91
|
+
68 | await passwordInput.fill(password);
|
|
92
|
+
69 | await expect(usernameInput).toHaveValue(username);
|
|
93
|
+
70 | await expect(passwordInput).toHaveValue(password);
|
|
94
|
+
71 | await expect(page.getByPlaceholder('Please input captcha')).not.toHaveValue('');
|
|
95
|
+
72 | const loginResponse = waitForApiResponse(page, 'POST', '/api/home/user/passport/login');
|
|
96
|
+
73 | await page.getByRole('button', { name: 'Login', exact: true }).click();
|
|
97
|
+
74 | const loginResponseValue = await loginResponse;
|
|
98
|
+
75 | if (!loginResponseValue.ok()) {
|
|
99
|
+
76 | throw new Error(
|
|
100
|
+
77 | `login failed: ${loginResponseValue.status()} ${await loginResponseValue.text()}`,
|
|
101
|
+
78 | );
|
|
102
|
+
79 | }
|
|
103
|
+
80 | await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
|
104
|
+
81 | }
|
|
105
|
+
82 | }
|
|
106
|
+
83 |
|
|
107
|
+
84 | async function registerCustomer(
|
|
108
|
+
85 | request: APIRequestContext,
|
|
109
|
+
86 | testInfo: TestInfo,
|
|
110
|
+
87 | ): Promise<{ password: string; username: string }> {
|
|
111
|
+
88 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
112
|
+
89 | const username = `e2e-address-${id}`;
|
|
113
|
+
90 | const password = 'address-e2e-password';
|
|
114
|
+
91 | const captchaResponse = await request.post('/api/captcha/create', {
|
|
115
|
+
92 | data: { scene: 'captcha-simple:simple' },
|
|
116
|
+
93 | });
|
|
117
|
+
94 | expect(captchaResponse.ok()).toBeTruthy();
|
|
118
|
+
95 | const captcha = (await captchaResponse.json()).data;
|
|
119
|
+
96 | expect(captcha?.id).toEqual(expect.any(String));
|
|
120
|
+
> 97 | expect(captcha?.token).toEqual(expect.any(String));
|
|
121
|
+
| ^ Error: expect(received).toEqual(expected) // deep equality
|
|
122
|
+
98 |
|
|
123
|
+
99 | const registerResponse = await request.post('/api/home/user/passport/register', {
|
|
124
|
+
100 | data: {
|
|
125
|
+
101 | username,
|
|
126
|
+
102 | email: `${username}@example.test`,
|
|
127
|
+
103 | password,
|
|
128
|
+
104 | passwordConfirm: password,
|
|
129
|
+
105 | captcha: { id: captcha.id, token: captcha.token },
|
|
130
|
+
106 | },
|
|
131
|
+
107 | });
|
|
132
|
+
108 | expect(registerResponse.ok()).toBeTruthy();
|
|
133
|
+
109 | const registration = (await registerResponse.json()).data;
|
|
134
|
+
110 | expect(registration?.jwt?.accessToken).toEqual(expect.any(String));
|
|
135
|
+
111 | const activateResponse = await request.post(passportTestActivateCurrentPath, {
|
|
136
|
+
112 | headers: { Authorization: `Bearer ${registration.jwt.accessToken}` },
|
|
137
|
+
113 | });
|
|
138
|
+
114 | expect(activateResponse.ok()).toBeTruthy();
|
|
139
|
+
115 | return { username, password };
|
|
140
|
+
116 | }
|
|
141
|
+
117 |
|
|
142
|
+
118 | function createAddressFixture(testInfo: TestInfo): IAddressFixture {
|
|
143
|
+
119 | const id = `${testInfo.workerIndex}-${testInfo.parallelIndex ?? testInfo.retry}-${Date.now()}`;
|
|
144
|
+
120 | return {
|
|
145
|
+
121 | recipientName: `E2E Address ${id}`,
|
|
146
|
+
122 | addressLine1: `1 Test Street ${id}`,
|
|
147
|
+
123 | city: `Original City ${id}`,
|
|
148
|
+
124 | updatedCity: `Updated City ${id}`,
|
|
149
|
+
125 | };
|
|
150
|
+
126 | }
|
|
151
|
+
127 |
|
|
152
|
+
128 | async function fillAddressForm(page: Page, fixture: IAddressFixture) {
|
|
153
|
+
129 | await page.getByPlaceholder('Recipient name').fill(fixture.recipientName);
|
|
154
|
+
130 | await page.getByPlaceholder('Phone').fill('15555550123');
|
|
155
|
+
131 | await page.getByPlaceholder('Country code').fill('US');
|
|
156
|
+
132 | await page.getByPlaceholder('Region').fill('California');
|
|
157
|
+
133 | await page.getByPlaceholder('City').fill(fixture.city);
|
|
158
|
+
134 | await page.getByPlaceholder('Postal code').fill('94105');
|
|
159
|
+
135 | await page.getByPlaceholder('Address line 1').fill(fixture.addressLine1);
|
|
160
|
+
136 | await page.getByPlaceholder('Address line 2').fill('Suite E2E');
|
|
161
|
+
137 | }
|
|
162
|
+
138 |
|
|
163
|
+
139 | async function createAddressThroughCustomerPage(
|
|
164
|
+
140 | browser: Browser,
|
|
165
|
+
141 | request: APIRequestContext,
|
|
166
|
+
142 | testInfo: TestInfo,
|
|
167
|
+
143 | ) {
|
|
168
|
+
144 | const customer = await registerCustomer(request, testInfo);
|
|
169
|
+
145 | const fixture = createAddressFixture(testInfo);
|
|
170
|
+
146 | const context = await browser.newContext();
|
|
171
|
+
147 | const page = await context.newPage();
|
|
172
|
+
148 | const pageErrors = collectPageErrors(page);
|
|
173
|
+
149 |
|
|
174
|
+
150 | await login(page, '/commerce/address', customer.username, customer.password, 'commerce');
|
|
175
|
+
151 | await expect(page).toHaveURL(/\/commerce\/address(?:\?|$)/);
|
|
176
|
+
152 | await expect(page.getByRole('heading', { name: 'Addresses' })).toBeVisible();
|
|
177
|
+
153 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
178
|
+
154 |
|
|
179
|
+
155 | await fillAddressForm(page, fixture);
|
|
180
|
+
156 | const createResponse = waitForAddressResponse(page, 'POST', `${addressActionPath}/createMine`);
|
|
181
|
+
157 | const mineResponse = waitForAddressMine(page);
|
|
182
|
+
158 | await page.getByRole('button', { name: 'Save address', exact: true }).click();
|
|
183
|
+
159 | await createResponse;
|
|
184
|
+
160 | await mineResponse;
|
|
185
|
+
161 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toBeVisible();
|
|
186
|
+
162 |
|
|
187
|
+
163 | return { context, fixture, page, pageErrors };
|
|
188
|
+
164 | }
|
|
189
|
+
165 |
|
|
190
|
+
166 | async function deleteAddressThroughCustomerPage(page: Page, fixture: IAddressFixture) {
|
|
191
|
+
167 | const card = page
|
|
192
|
+
168 | .locator('article')
|
|
193
|
+
169 | .filter({ has: page.getByRole('heading', { name: fixture.recipientName }) });
|
|
194
|
+
170 | const deleteResponse = waitForAddressResponse(page, 'DELETE', /\/deleteMine\/[^/]+$/);
|
|
195
|
+
171 | const mineResponse = waitForAddressMine(page);
|
|
196
|
+
172 | await card.getByRole('button', { name: 'Delete', exact: true }).click();
|
|
197
|
+
173 | await deleteResponse;
|
|
198
|
+
174 | await mineResponse;
|
|
199
|
+
175 | await expect(page.getByRole('heading', { name: fixture.recipientName })).toHaveCount(0);
|
|
200
|
+
176 | await expect(page.getByText('No addresses yet.')).toBeVisible();
|
|
201
|
+
177 | }
|
|
202
|
+
178 |
|
|
203
|
+
179 | test(
|
|
204
|
+
180 | 'ATP-SSR-01: anonymous Commerce HTML is public and hydration completes',
|
|
205
|
+
181 | { tag: ['@web', '@smoke'] },
|
|
206
|
+
182 | async ({ page, request }) => {
|
|
207
|
+
183 | const response = await request.get('/commerce');
|
|
208
|
+
184 | expect(response.ok()).toBeTruthy();
|
|
209
|
+
185 | const html = await response.text();
|
|
210
|
+
186 | const normalizedHtml = html.toLowerCase();
|
|
211
|
+
187 | expect(normalizedHtml).not.toContain('data-zova-hydrated');
|
|
212
|
+
188 |
|
|
213
|
+
189 | for (const marker of privateMarkers) {
|
|
214
|
+
190 | expect(normalizedHtml).not.toContain(`"${marker}"`);
|
|
215
|
+
191 | }
|
|
216
|
+
192 |
|
|
217
|
+
193 | const pageErrors = collectPageErrors(page);
|
|
218
|
+
194 | const documentResponse = await page.goto('/commerce', { waitUntil: 'load' });
|
|
219
|
+
195 | expect(documentResponse?.ok()).toBeTruthy();
|
|
220
|
+
196 | await expect(page).toHaveURL(/\/commerce(?:\/|$)/);
|
|
221
|
+
197 | await expect(page.locator('html')).toHaveAttribute('data-zova-hydrated', 'commerce');
|
|
222
|
+
```
|