Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions .github/workflows/e2e_api_tests.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
name: E2E_API Tests

on:
# Runs once a day only, to keep Actions cost down. Pull requests and pushes
# to develop no longer trigger this suite -- use the "Run workflow" button
# (workflow_dispatch) below to test a branch on demand.
# The FULL suite runs once a day only, to keep Actions cost down (#3350).
# Target start time is 9:00 AM BDT. GitHub queues scheduled runs and
# releases them once capacity frees up: on this repository that delay has
# been roughly 70 minutes every day, so 02:00 UTC (nominally 8:00 AM BDT)
# is what actually starts the run near 9:00 AM BDT. Check the real start
# times of recent scheduled runs before "correcting" this to 03:00 UTC.
schedule:
- cron: 0 2 * * *
# Pull requests run ONLY the curated smoke subset (5 shards,
# tests/pw/utils/pr-smoke-specs.json, ~1/3 of the full-matrix cost) so PRs
# keep a fast e2e signal without the full-suite fan-out #3350 removed.
pull_request:
branches: [develop]
paths-ignore:
- '**.md'
- '**.txt'
- 'docs/**'
- '.github/ISSUE_TEMPLATE/**'
- '**/*.po'
- '**/*.pot'
- '**/*.svg'
- 'assets/images/**'
# workflow can be manually triggered
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -270,9 +282,16 @@ jobs:
if: always() && needs.build_lite.result == 'success' && (needs.build_pro.result == 'success' || needs.build_pro.result == 'skipped')
strategy:
fail-fast: false
# PRs run the curated smoke subset (tests/pw/utils/pr-smoke-specs.json,
# ~79 min serial) across 8 shards; the nightly schedule and manual
# dispatch run the full suite across 12. This caps a PR push at
# ~12 jobs / ~165 runner-minutes instead of 16 jobs / ~370, so
# concurrent PRs stop starving the org's runner pool. 8 is where the
# returns flatten: each shard carries ~4.6 min of fixed wp-env setup,
# so past ~12 min of tests per shard the setup starts to dominate.
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
shardTotal: [12]
shardIndex: ${{ github.event_name == 'pull_request' && fromJSON('[1, 2, 3, 4, 5, 6, 7, 8]') || fromJSON('[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]') }}
shardTotal: ${{ github.event_name == 'pull_request' && fromJSON('[8]') || fromJSON('[12]') }}

steps:
- name: Download dokan-lite build artifact
Expand Down Expand Up @@ -480,11 +499,13 @@ jobs:

# Resolve the spec list for this shard from the committed duration baseline.
# Falls back to alphabetical sharding if the baseline is missing.
# PRs pass --smoke to restrict the pack to pr-smoke-specs.json.
- name: Resolve balanced shard spec list
id: shard-specs
working-directory: tests/pw
run: |
node ./utils/getShardSpecs.js ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} > /tmp/shard-specs.txt
node ./utils/getShardSpecs.js ${{ matrix.shardIndex }} ${{ matrix.shardTotal }} \
${{ github.event_name == 'pull_request' && '--smoke' || '' }} > /tmp/shard-specs.txt
echo "spec count: $(wc -l < /tmp/shard-specs.txt)"

# Emit a per-shard manifest (no GITHUB_STEP_SUMMARY write here — that
Expand Down Expand Up @@ -930,9 +951,13 @@ jobs:
# baseline. Uploaded as artifact so maintainers can periodically
# commit it to tests/pw/utils/shard-durations.json to keep
# balanced sharding accurate as the suite evolves.
# Skipped on PRs: smoke runs only measure the smoke subset, and a
# partial baseline committed by mistake would wreck the bin-pack.
- name: Aggregate spec-duration baseline
id: aggregate-baseline
if: always() && steps.download-test-artifacts.outcome == 'success'
if: |
always() && steps.download-test-artifacts.outcome == 'success'
&& github.event_name != 'pull_request'
working-directory: tests/pw
run: |
node ./utils/aggregateSpecDurations.js \
Expand Down
7 changes: 7 additions & 0 deletions tests/pw/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ Every new test MUST carry one Lite/Pro gate (`@lite`, `@liteOnly`, or `@pro`) an
| **PR** | `DOKAN_PRO=true`, fast PR-gate run — `e2e_tests` only, skips `@serial`/`@exploratory`. Command: `NO_SETUP=true npx playwright test --project=e2e_tests --grep-invert "@exploratory\|@serial"` |
| **Full Suite**| `DOKAN_PRO=true`, full bootstrap if needed. E2E + API. Commands: `npm run docker:full && npm run test:e2e && npm run test:api` |

These are *local* run modes. CI is different: pull requests run only the curated
smoke subset listed in `utils/pr-smoke-specs.json` (resolved per shard by
`utils/getShardSpecs.js --smoke`; trailing `/` = whole feature folder, otherwise
exact spec-file match), while the nightly schedule and manual `workflow_dispatch`
run the full sharded matrix. To gate a feature on PRs, add its folder to that
JSON list — tags don't control CI spec selection.

Comment on lines +225 to +231

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document the develop-only PR trigger.

The workflow runs this smoke path only for pull requests targeting develop, but the documentation says “pull requests” generally. Qualify this as “pull requests targeting develop” to avoid misleading contributors about other base branches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/pw/setup.md` around lines 225 - 231, Update the CI documentation near
the smoke subset description to specify that this pull-request workflow applies
only to pull requests targeting develop, while preserving the existing nightly
and manual workflow descriptions.

---

## 9. Hard preconditions (skill checks these before running)
Expand Down
14 changes: 8 additions & 6 deletions tests/pw/tests/e2e/products/newProducts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,18 @@ test.describe('Products (React) functionality', () => {
await expect(products.rowByName(newProductsData.seededProductName)).toBeVisible();
});

test('vendor sees an empty Draft status tab (React)', { tag: ['@lite', '@vendor', '@new-ui'] }, async () => {
// Specs sharing this vendor's wp-env legitimately leave non-published products
// behind (product-form-manager saves a draft), so assert the tab filters the
// seeded Published product out rather than that it is globally empty. The
// empty-state banner itself stays covered by the nonsense-search case below.
test('vendor Draft status tab excludes published products (React)', { tag: ['@lite', '@vendor', '@new-ui'] }, async () => {
await products.clickTab(/^Draft/);
await expect(products.rows, 'draft tab is empty').toHaveCount(0);
await expect(products.emptyState, 'empty-state banner shows for empty Draft tab').toBeVisible();
await expect(products.rowByName(newProductsData.seededProductName), 'published product absent from Draft tab').toBeHidden();
});

test('vendor sees an empty Pending Review status tab (React)', { tag: ['@lite', '@vendor', '@new-ui'] }, async () => {
test('vendor Pending Review status tab excludes published products (React)', { tag: ['@lite', '@vendor', '@new-ui'] }, async () => {
await products.clickTab(/^Pending Review/);
await expect(products.rows, 'pending tab is empty').toHaveCount(0);
await expect(products.emptyState, 'empty-state banner shows for empty Pending tab').toBeVisible();
await expect(products.rowByName(newProductsData.seededProductName), 'published product absent from Pending Review tab').toBeHidden();
});

test('vendor can filter by In stock status tab (React)', { tag: ['@lite', '@vendor', '@new-ui'] }, async () => {
Expand Down
43 changes: 35 additions & 8 deletions tests/pw/utils/getShardSpecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@
/*
* Resolves the spec list for a given Playwright shard using a duration baseline.
*
* Usage: node getShardSpecs.js <shardIndex> <shardTotal>
* Usage: node getShardSpecs.js <shardIndex> <shardTotal> [--smoke]
*
* Strategy:
* 1. Read tests/pw/utils/shard-durations.json (committed baseline of spec → ms).
* 2. Discover every *.spec.ts under tests/e2e (so newly-added specs are not lost).
* 3. Greedy bin-pack longest-first into N bins.
* 4. Print spec paths (one per line, relative to tests/pw) for the requested shard.
* 3. With --smoke, keep only specs listed in pr-smoke-specs.json.
* 4. Greedy bin-pack longest-first into N bins.
* 5. Print spec paths (one per line, relative to tests/pw) for the requested shard.
*
* If the baseline is missing, exits 0 with no output so the workflow can fall
* back to Playwright's built-in --shard.
* back to Playwright's built-in --shard — except in --smoke mode, where falling
* back would silently run the FULL suite; there we pack the smoke list with
* equal weights instead.
Comment on lines +5 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required documentation version placeholder.

Proposed fix
  * equal weights instead.
+ *
+ * `@since` DOKAN_SINCE
  */

As per coding guidelines, new code documentation must use the literal placeholder @since DOKAN_SINCE.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* Usage: node getShardSpecs.js <shardIndex> <shardTotal> [--smoke]
*
* Strategy:
* 1. Read tests/pw/utils/shard-durations.json (committed baseline of spec → ms).
* 2. Discover every *.spec.ts under tests/e2e (so newly-added specs are not lost).
* 3. Greedy bin-pack longest-first into N bins.
* 4. Print spec paths (one per line, relative to tests/pw) for the requested shard.
* 3. With --smoke, keep only specs listed in pr-smoke-specs.json.
* 4. Greedy bin-pack longest-first into N bins.
* 5. Print spec paths (one per line, relative to tests/pw) for the requested shard.
*
* If the baseline is missing, exits 0 with no output so the workflow can fall
* back to Playwright's built-in --shard.
* back to Playwright's built-in --shard — except in --smoke mode, where falling
* back would silently run the FULL suite; there we pack the smoke list with
* equal weights instead.
* Usage: node getShardSpecs.js <shardIndex> <shardTotal> [--smoke]
*
* Strategy:
* 1. Read tests/pw/utils/shard-durations.json (committed baseline of spec → ms).
* 2. Discover every *.spec.ts under tests/e2e (so newly-added specs are not lost).
* 3. With --smoke, keep only specs listed in pr-smoke-specs.json.
* 4. Greedy bin-pack longest-first into N bins.
* 5. Print spec paths (one per line, relative to tests/pw) for the requested shard.
*
* If the baseline is missing, exits 0 with no output so the workflow can fall
* back to Playwright's built-in --shard — except in --smoke mode, where falling
* back would silently run the FULL suite; there we pack the smoke list with
* equal weights instead.
*
* `@since` DOKAN_SINCE
*/
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/pw/utils/getShardSpecs.js` around lines 5 - 17, Add the required
documentation version placeholder `@since DOKAN_SINCE` to the new documentation
block in the shard-specification utility, preserving the existing usage and
strategy content.

Source: Coding guidelines

*/

'use strict';

const fs = require('fs');
const path = require('path');

const [, , shardIndexArg, shardTotalArg] = process.argv;
const cliArgs = process.argv.slice(2);
const smoke = cliArgs.includes('--smoke');
const [shardIndexArg, shardTotalArg] = cliArgs.filter(a => !a.startsWith('--'));
const shardIndex = parseInt(shardIndexArg, 10);
const shardTotal = parseInt(shardTotalArg, 10);
if (!shardIndex || !shardTotal || shardIndex < 1 || shardIndex > shardTotal) {
Expand All @@ -31,12 +36,14 @@ const pwRoot = path.resolve(__dirname, '..');
const baselinePath = path.join(__dirname, 'shard-durations.json');
const e2eRoot = path.join(pwRoot, 'tests', 'e2e');

if (!fs.existsSync(baselinePath)) {
if (!fs.existsSync(baselinePath) && !smoke) {
// No baseline → fall back to alphabetical sharding upstream.
process.exit(0);
}

const baseline = JSON.parse(fs.readFileSync(baselinePath, 'utf8'));
const baseline = fs.existsSync(baselinePath)
? JSON.parse(fs.readFileSync(baselinePath, 'utf8'))
: { specs: [] };
const baselineByFile = new Map();
for (const entry of baseline.specs || []) {
baselineByFile.set(entry.file, entry.ms || 0);
Expand Down Expand Up @@ -64,7 +71,27 @@ if (!fs.existsSync(e2eRoot)) {
process.exit(0);
}

const allSpecs = walkSpecs(e2eRoot)
let specFiles = walkSpecs(e2eRoot).map(f => f.split(path.sep).join('/'));

if (smoke) {
// Fail closed: silently degrading to the full 194-spec suite on a 5-shard
// PR matrix is the exact cost blow-up --smoke exists to prevent.
const smokePath = path.join(__dirname, 'pr-smoke-specs.json');
if (!fs.existsSync(smokePath)) {
console.error('pr-smoke-specs.json missing — refusing to pack the full suite in smoke mode');
process.exit(2);
}
const { include } = JSON.parse(fs.readFileSync(smokePath, 'utf8'));
specFiles = specFiles.filter(f =>
include.some(entry => (entry.endsWith('/') ? f.startsWith(entry) : f === entry))
);
if (specFiles.length === 0) {
console.error('pr-smoke-specs.json matched no specs — check the include list');
process.exit(2);
}
}

const allSpecs = specFiles
.map(file => ({
file,
// Default newly-added (unmeasured) specs to the global mean so they
Expand Down
32 changes: 32 additions & 0 deletions tests/pw/utils/pr-smoke-specs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"description": "Curated e2e smoke subset run on pull_request events (the full suite still runs on the nightly schedule and manual workflow_dispatch). Entries are paths relative to tests/e2e/: a trailing slash includes the whole feature folder, otherwise the entry must match a spec file exactly. Consumed by getShardSpecs.js --smoke.",
"include": [
"products/",
"product-form-manager/",
"vendor-products/",
"orders/",
"my-orders/",
"manual-order/",
"refunds/",
"coupons/",
"dashboard/",
"withdraws/",
"reverse-withdraws/",
"payments/",
"commission/",
"settings/",
"vendor-settings/",
"vendor/",
"stores/",
"store-listing/",
"single-store/",
"single-product/",
"shop/",
"customer/",
"plugin/",
"admin/admin.spec.ts",
"admin/adminDashboardHome.spec.ts",
"admin/adminVendors.spec.ts",
"admin/adminWithdraw.spec.ts"
]
}
Loading