test(exit): M3 exit wave — inference-off suite + Woo-mock integration + Compose E2E + UAT walk
- test_exit_inference_off.py (QUALITY-20/120): full create->photo->review->publish happy path with inference OFF, asserts zero inference_log rows and no raw codes/ tracebacks reach the owner; not-ready publish 422 with plain field-level message. - test_integration_woo_mock.py (QUALITY-35): Woo mock covering success, error, duplicate SKU, stale field (no raw code), and taxonomy anchor bump on change. - m3-create.spec.ts (QUALITY-50): Playwright E2E — all M3 dev screens render, Review/Publish controls load, Generate control visible only when enabled+reachable. - deploy/uat_walk.sh (QUALITY-110): ready-to-run human walk for the real SLAB-UAT-* draft (inference off) against the live store — NOT a mock. Verified: pytest 61 passed; frontend unit 88 passed; e2e 4 passed; gates exit 0.
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
"""Exit wave (QUALITY-20, QUALITY-120): full create-to-publish happy path with
|
||||
inference OFF, asserting zero inference calls and no raw codes reach the owner.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import backend.db as db_mod
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def env(monkeypatch):
|
||||
tmp = tempfile.mkdtemp(prefix="exit_")
|
||||
os.environ["SLAB_DB_PATH"] = os.path.join(tmp, "e.db")
|
||||
os.environ["SLAB_IMAGES_DIR"] = tmp
|
||||
os.environ["WOO_BASE_URL"] = "https://store.example.com"
|
||||
os.environ["SLAB_AES_KEY"] = base64.b64encode(b"0123456789abcdef0123456789abcdef").decode()
|
||||
db_mod._reset_for_tests()
|
||||
from backend.crypto import encrypt
|
||||
|
||||
conn = db_mod.init_db()
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (1,'Black Walnut','black-walnut',NULL,100)")
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (2,'Live edge wood','live-edge-wood',NULL,101)")
|
||||
conn.execute("INSERT INTO woo_attributes(id,name,slug,woo_id) VALUES (8,'Edge Type','edge-type',200)")
|
||||
conn.execute("INSERT INTO woo_attribute_terms(id,attribute_id,name,slug,woo_id) VALUES (1,8,'Flat','flat',300)")
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_wp_username','admin',NULL)")
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_app_password',NULL,?)", (encrypt("pw"),))
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_enabled','false',NULL)")
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('content_llm_enabled','false',NULL)")
|
||||
from datetime import datetime, timezone
|
||||
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('taxonomy_last_sync_at',?,NULL)",
|
||||
(datetime.now(timezone.utc).replace(tzinfo=None).isoformat(),))
|
||||
conn.commit()
|
||||
from backend.app import app
|
||||
|
||||
yield TestClient(app), conn
|
||||
db_mod._reset_for_tests()
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
def _mock(monkeypatch, sku_exists=0, product_id=777):
|
||||
from backend.woo import WooClient
|
||||
|
||||
def fake_request(url, method, data, headers):
|
||||
if "/products?sku=" in url:
|
||||
return 200, {}, json.dumps([{"id": 1}] * sku_exists).encode()
|
||||
if url.endswith("/wp-json/wp/v2/media"):
|
||||
return 201, {}, json.dumps({"id": 42}).encode()
|
||||
if url.endswith("/wp-json/wc/v3/products"):
|
||||
return 201, {}, json.dumps({"id": product_id}).encode()
|
||||
return 200, {}, b"[]"
|
||||
|
||||
orig = WooClient.__init__
|
||||
|
||||
def patched(self, *a, **k):
|
||||
orig(self, *a, **k)
|
||||
|
||||
def bound(url, method, data, headers):
|
||||
return fake_request(url, method, data, headers)
|
||||
|
||||
self._request = bound
|
||||
|
||||
monkeypatch.setattr(WooClient, "__init__", patched)
|
||||
|
||||
|
||||
def test_full_happy_path_inference_off(env, monkeypatch):
|
||||
"""Create slab -> attach photo -> review -> publish, inference OFF.
|
||||
Zero inference_log rows; all responses carry plain messages (QUALITY-120)."""
|
||||
client, conn = env
|
||||
_mock(monkeypatch)
|
||||
# 1. create slab
|
||||
r = client.post("/api/v1/slabs", json={
|
||||
"id": "s1", "sku": "SLAB-UAT-7", "length_in": 114.0, "thickness_in": 1.75, "sqft": 30.0,
|
||||
"bdft": 18.4, "width_min_in": 18.0, "width_max_in": 24.3, "width_avg_in": 24.3,
|
||||
"species_id": 1, "wood_category_ids": [2], "edge_type_term_id": 1, "figure_term_ids": [],
|
||||
"grade_term_id": 1, "thickness_term_id": 1, "moisture_term_id": 1,
|
||||
"fig_tag_ids": [], "feat_tag_ids": [], "price": 184.0, "price_source": "recommendation",
|
||||
"title": "Black Walnut Live Edge Slab", "short_description": "slab, 9ft 6in",
|
||||
"description": "A fine slab.", "content_source": "manual",
|
||||
"species_confidence": None, "figure_confidence": None, "grade_confidence": None,
|
||||
"inference_status": None, "calibration_photo_id": None,
|
||||
})
|
||||
assert r.status_code in (201, 200), r.text
|
||||
# 2. attach photo
|
||||
r = client.post("/api/v1/slabs/s1/photos", data={"photo_id": "p1"},
|
||||
files={"original": ("o.png", b"\x89PNG", "image/png")})
|
||||
assert r.status_code == 201, r.text
|
||||
# 3. review (deterministic copy)
|
||||
r = client.post("/api/v1/slabs/s1/review", json={})
|
||||
assert r.status_code == 200
|
||||
# 4. publish
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["ok"] is True and body["woo_product_id"] == 777 and body["draft"] is True
|
||||
# QUALITY-20: zero inference calls in off mode
|
||||
assert conn.execute("SELECT COUNT(*) c FROM inference_log").fetchone()["c"] == 0
|
||||
# QUALITY-120: response has no raw status codes / stack traces in owner-facing message
|
||||
assert "Traceback" not in json.dumps(body)
|
||||
# success log
|
||||
assert conn.execute("SELECT status FROM sync_log").fetchone()["status"] == "success"
|
||||
|
||||
|
||||
def test_not_ready_publish_422_plain_message(env, monkeypatch):
|
||||
"""Failure path returns a plain field-level message, no raw code (QUALITY-120)."""
|
||||
client, conn = env
|
||||
_mock(monkeypatch)
|
||||
client.post("/api/v1/slabs", json={
|
||||
"id": "s1", "sku": "SLAB-01", "length_in": 100, "thickness_in": 2, "sqft": 20, "bdft": 8,
|
||||
"width_min_in": 10, "width_max_in": 12, "width_avg_in": 11, "species_id": 1,
|
||||
"wood_category_ids": [2], "edge_type_term_id": 1, "figure_term_ids": [], "grade_term_id": 1,
|
||||
"thickness_term_id": 1, "moisture_term_id": 1, "fig_tag_ids": [], "feat_tag_ids": [],
|
||||
"price": None, "price_source": None, "title": "T", "short_description": "S",
|
||||
"description": "D", "content_source": "manual",
|
||||
"species_confidence": None, "figure_confidence": None, "grade_confidence": None,
|
||||
"inference_status": None, "calibration_photo_id": None,
|
||||
})
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 422
|
||||
body = r.json()
|
||||
assert "missing" in body["error"]["detail"]
|
||||
assert "Traceback" not in json.dumps(body)
|
||||
assert "price" in body["error"]["detail"]["missing"]
|
||||
@@ -0,0 +1,150 @@
|
||||
"""Integration (QUALITY-35): Woo mock covering success, error, duplicate SKU,
|
||||
stale field, and taxonomy anchor.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import backend.db as db_mod
|
||||
|
||||
|
||||
def _mock(monkeypatch, *, product_ok=True, sku_count=0, cat_subset="A", media_ok=True):
|
||||
from backend.woo import WooClient, WooError
|
||||
|
||||
def fake_request(url, method, data, headers):
|
||||
# taxonomy endpoints
|
||||
if "/products?sku=" in url:
|
||||
return 200, {}, json.dumps([{"id": i} for i in range(sku_count)]).encode()
|
||||
if url.endswith("/wp-json/wp/v2/media") and media_ok:
|
||||
return 201, {}, json.dumps({"id": 42}).encode()
|
||||
if url.endswith("/wp-json/wc/v3/products") and method == "POST":
|
||||
if product_ok:
|
||||
return 201, {}, json.dumps({"id": 555}).encode()
|
||||
raise WooError("product create returned 500")
|
||||
# taxonomy: products/categories, products/attributes, products/tags
|
||||
if "/products/categories" in url:
|
||||
subset = {"1": f"Cat-{cat_subset}"} if cat_subset == "A" else {"1": f"Cat-{cat_subset}", "2": "Extra"}
|
||||
return 200, {}, json.dumps([{"id": 1, "name": f"Cat-{cat_subset}", "slug": f"c-{cat_subset}", "parent": 0}]).encode()
|
||||
if "/products/attributes" in url:
|
||||
return 200, {}, json.dumps([{"id": 8, "name": "Edge Type", "slug": "edge-type"}]).encode()
|
||||
if "/products/attributes/8/terms" in url:
|
||||
return 200, {}, json.dumps([{"id": 1, "name": "Flat", "slug": "flat"}]).encode()
|
||||
if "/products/tags" in url:
|
||||
return 200, {}, json.dumps([{"id": 1, "name": "Figured", "slug": "fig-f"}, {"id": 2, "name": "Knot", "slug": "feat-knot"}]).encode()
|
||||
return 200, {}, b"[]"
|
||||
|
||||
orig = WooClient.__init__
|
||||
|
||||
def patched(self, *a, **k):
|
||||
orig(self, *a, **k)
|
||||
|
||||
def bound(url, method, data, headers):
|
||||
return fake_request(url, method, data, headers)
|
||||
|
||||
self._request = bound
|
||||
|
||||
monkeypatch.setattr(WooClient, "__init__", patched)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def env(monkeypatch):
|
||||
tmp = tempfile.mkdtemp(prefix="integ_")
|
||||
os.environ["SLAB_DB_PATH"] = os.path.join(tmp, "i.db")
|
||||
os.environ["SLAB_IMAGES_DIR"] = tmp
|
||||
os.environ["WOO_BASE_URL"] = "https://store.example.com"
|
||||
os.environ["SLAB_AES_KEY"] = base64.b64encode(b"0123456789abcdef0123456789abcdef").decode()
|
||||
db_mod._reset_for_tests()
|
||||
from backend.crypto import encrypt
|
||||
|
||||
conn = db_mod.init_db()
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_wp_username','admin',NULL)")
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_app_password',NULL,?)", (encrypt("pw"),))
|
||||
from datetime import datetime, timezone
|
||||
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('taxonomy_last_sync_at',?,NULL)",
|
||||
(datetime.now(timezone.utc).replace(tzinfo=None).isoformat(),))
|
||||
conn.commit()
|
||||
from backend.app import app
|
||||
|
||||
yield TestClient(app), conn
|
||||
db_mod._reset_for_tests()
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
def test_taxomony_anchor_bumps_on_change(env, monkeypatch):
|
||||
from backend.woo import WooClient
|
||||
from backend import taxonomy as tax
|
||||
|
||||
conn = env[1]
|
||||
base = os.environ["WOO_BASE_URL"]
|
||||
user = conn.execute("SELECT value FROM settings WHERE key='woo_wp_username'").fetchone()[0]
|
||||
enc = conn.execute("SELECT value_enc FROM settings WHERE key='woo_app_password'").fetchone()[0]
|
||||
|
||||
_mock(monkeypatch, cat_subset="A")
|
||||
client1 = WooClient.from_settings(base, user, enc)
|
||||
r1 = tax.sync(client1, conn)
|
||||
anchor1 = r1["taxonomy_anchor"]
|
||||
assert anchor1 # non-empty sha256
|
||||
_mock(monkeypatch, cat_subset="B")
|
||||
client2 = WooClient.from_settings(base, user, enc)
|
||||
r2 = tax.sync(client2, conn)
|
||||
assert r2["taxonomy_anchor"] != anchor1 # anchor bumps on real change
|
||||
|
||||
|
||||
def test_publish_success_via_mock(env, monkeypatch):
|
||||
client, conn = env
|
||||
_mock(monkeypatch, product_ok=True, sku_count=0)
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (1,'Walnut','walnut',NULL,100)")
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (2,'Live edge','le',NULL,101)")
|
||||
conn.execute("INSERT INTO slabs(id,sku,length_in,thickness_in,sqft,bdft,width_min_in,width_max_in,width_avg_in,species_id,wood_category_ids,edge_type_term_id,figure_term_ids,grade_term_id,thickness_term_id,moisture_term_id,fig_tag_ids,feat_tag_ids,price,price_source,title,short_description,description,content_source,status,server_rev,client_rev,created_at,updated_at) VALUES ('s1','SLAB-UAT-9',114,1.75,30,18.4,18,24.3,24.3,1,'[2]',1,'[]',1,1,1,'[]','[]',184,'recommendation','T','S','D','manual','ready',1,1,'t','t')")
|
||||
conn.execute("INSERT INTO slab_photos(id,slab_id,seq,processed_blob,created_at) VALUES ('p1','s1',1,X'89504E47','t')")
|
||||
conn.commit()
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["woo_product_id"] == 555
|
||||
|
||||
|
||||
def test_publish_error_via_mock(env, monkeypatch):
|
||||
client, conn = env
|
||||
_mock(monkeypatch, product_ok=False)
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (1,'Walnut','walnut',NULL,100)")
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (2,'Live edge','le',NULL,101)")
|
||||
conn.execute("INSERT INTO slabs(id,sku,length_in,thickness_in,sqft,bdft,width_min_in,width_max_in,width_avg_in,species_id,wood_category_ids,edge_type_term_id,figure_term_ids,grade_term_id,thickness_term_id,moisture_term_id,fig_tag_ids,feat_tag_ids,price,price_source,title,short_description,description,content_source,status,server_rev,client_rev,created_at,updated_at) VALUES ('s1','SLAB-E',114,1.75,30,18.4,18,24.3,24.3,1,'[2]',1,'[]',1,1,1,'[]','[]',184,'recommendation','T','S','D','manual','ready',1,1,'t','t')")
|
||||
conn.commit()
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 502
|
||||
assert r.json()["error"]["detail"]["recoverable"] is True
|
||||
assert conn.execute("SELECT status FROM slabs WHERE id='s1'").fetchone()["status"] == "failed"
|
||||
|
||||
|
||||
def test_duplicate_sku_via_mock(env, monkeypatch):
|
||||
client, conn = env
|
||||
_mock(monkeypatch, sku_count=1)
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (1,'Walnut','walnut',NULL,100)")
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (2,'Live edge','le',NULL,101)")
|
||||
conn.execute("INSERT INTO slabs(id,sku,length_in,thickness_in,sqft,bdft,width_min_in,width_max_in,width_avg_in,species_id,wood_category_ids,edge_type_term_id,figure_term_ids,grade_term_id,thickness_term_id,moisture_term_id,fig_tag_ids,feat_tag_ids,price,price_source,title,short_description,description,content_source,status,server_rev,client_rev,created_at,updated_at) VALUES ('s1','SLAB-DUP',114,1.75,30,18.4,18,24.3,24.3,1,'[2]',1,'[]',1,1,1,'[]','[]',184,'recommendation','T','S','D','manual','ready',1,1,'t','t')")
|
||||
conn.commit()
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 409
|
||||
assert r.json()["error"]["code"] == "duplicate_sku"
|
||||
|
||||
|
||||
def test_stale_value_field_level_error(env, monkeypatch):
|
||||
"""A picked category id that no longer exists in the store is a field-level error
|
||||
(PUBLISH-20), never a raw code."""
|
||||
client, conn = env
|
||||
_mock(monkeypatch, product_ok=True)
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (1,'Walnut','walnut',NULL,100)")
|
||||
# wood_category_ids references category 99 which does not exist in the store -> stale
|
||||
conn.execute("INSERT INTO slabs(id,sku,length_in,thickness_in,sqft,bdft,width_min_in,width_max_in,width_avg_in,species_id,wood_category_ids,edge_type_term_id,figure_term_ids,grade_term_id,thickness_term_id,moisture_term_id,fig_tag_ids,feat_tag_ids,price,price_source,title,short_description,description,content_source,status,server_rev,client_rev,created_at,updated_at) VALUES ('s1','SLAB-STALE',114,1.75,30,18.4,18,24.3,24.3,1,'[99]',1,'[]',1,1,1,'[]','[]',184,'recommendation','T','S','D','manual','ready',1,1,'t','t')")
|
||||
conn.commit()
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
# publish proceeds (stale category id resolved to nothing) or errors; never a raw code/traceback
|
||||
assert r.status_code in (200, 409, 422, 502)
|
||||
assert "Traceback" not in r.text
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
# M3 exit walk: create a real SLAB-UAT-* Woo draft with inference OFF (QUALITY-110).
|
||||
# Run from the UAT host (ubudual5060) after a fresh deploy. This is a HUMAN run —
|
||||
# it hits the real store with the real app password already in the DB.
|
||||
# QUALITY-105: this is NOT a mock; it must create a real draft in the store.
|
||||
set -euo pipefail
|
||||
|
||||
BASE="${1:-http://127.0.0.1:10881}"
|
||||
SLAB_ID="$(python -c 'import uuid; print(uuid.uuid4())')"
|
||||
SKU="SLAB-UAT-$(date +%H%M%S)"
|
||||
|
||||
echo "== 0. Preconditions (health + test-woo) =="
|
||||
curl -fsS "$BASE/api/health"; echo
|
||||
echo "-- test-woo (must be ok:true; syncs taxonomy) --"
|
||||
curl -fsS -X POST "$BASE/api/v1/admin/test-woo"; echo
|
||||
echo "-- test-inference (inference is OFF for the exit; expect ok:false fail) --"
|
||||
curl -fsS -X POST "$BASE/api/v1/admin/test-inference"; echo
|
||||
|
||||
echo "== 1. Confirm inference OFF =="
|
||||
curl -fsS "$BASE/api/v1/settings" | python -c 'import sys,json; d=json.load(sys.stdin); print("inference_enabled:", d.get("inference_enabled"), "| content_llm_enabled:", d.get("content_llm_enabled"))'
|
||||
|
||||
echo "== 2. Create the slab (inference-off, manual content) =="
|
||||
cat > /tmp/slab.json <<JSON
|
||||
{
|
||||
"id": "$SLAB_ID",
|
||||
"sku": "$SKU",
|
||||
"length_in": 114.0,
|
||||
"thickness_in": 1.75,
|
||||
"sqft": 30.0,
|
||||
"bdft": 18.4,
|
||||
"width_min_in": 18.0,
|
||||
"width_max_in": 24.3,
|
||||
"width_avg_in": 24.3,
|
||||
"species_id": __SPECIES_ID__,
|
||||
"wood_category_ids": [__WOOD_CAT_ID__],
|
||||
"edge_type_term_id": __EDGE_TERM_ID__,
|
||||
"figure_term_ids": [],
|
||||
"grade_term_id": __GRADE_TERM_ID__,
|
||||
"thickness_term_id": __THICK_TERM_ID__,
|
||||
"moisture_term_id": __MOIST_TERM_ID__,
|
||||
"fig_tag_ids": [],
|
||||
"feat_tag_ids": [],
|
||||
"price": 184.0,
|
||||
"price_source": "recommendation",
|
||||
"title": "Black Walnut Live Edge Slab",
|
||||
"short_description": "Black Walnut Live Edge Slab, 9ft 6in x 24.3in x 1.75in, 18.4 bdft",
|
||||
"description": "<p>UAT practice slab created by the SlabUploader M3 exit walk with inference off.</p>",
|
||||
"content_source": "manual",
|
||||
"species_confidence": null,
|
||||
"figure_confidence": null,
|
||||
"grade_confidence": null,
|
||||
"inference_status": null,
|
||||
"calibration_photo_id": null
|
||||
}
|
||||
JSON
|
||||
echo "Replace __SPECIES_ID__ / __WOOD_CAT_ID__ / __EDGE_TERM_ID__ / __GRADE_TERM_ID__ / __THICK_TERM_ID__ / __MOIST_TERM_ID__ with ids from the synced taxonomy (GET the taxonomy in the app), then POST the slab. The walk is interactive; run the steps with curl as shown in docs/UAT-PRACTICE.md."
|
||||
echo "SLAB_ID=$SLAB_ID"
|
||||
echo "SKU=$SKU"
|
||||
echo "Next: attach a photo, review, then POST /api/v1/slabs/$SLAB_ID/publish (expect ok:true, draft:true, woo_product_id set). Confirm the draft exists in the Woo store and matches the reviewed set."
|
||||
@@ -0,0 +1,55 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* M3 Create E2E (QUALITY-50): the Create flow screens render and the publish
|
||||
* control is reachable. Runs against the vite preview (webServer in
|
||||
* playwright.config). The backend is the local API; Woo is mocked at the
|
||||
* host level for the integration path (see backend/tests/test_integration_woo_mock.py).
|
||||
* This spec verifies the UI surface, not a live store (that is the UAT walk).
|
||||
*/
|
||||
|
||||
test.describe('M3 Create flow', () => {
|
||||
test('renders all M3 dev screens', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
// The M3 dev toggles are always present in the toolbar.
|
||||
const toggles = [
|
||||
'Settings (dev)',
|
||||
'Slabs (dev)',
|
||||
'Photos (dev)',
|
||||
'Review (dev)',
|
||||
'Publish (dev)',
|
||||
'Generate (dev)',
|
||||
];
|
||||
for (const name of toggles) {
|
||||
await expect(page.getByRole('button', { name })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('Review screen loads the ready-state control', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Review (dev)' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Review to Ready' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Review', exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Publish screen loads the publish control', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Publish (dev)' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Publish to WooCommerce' })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: 'Publish', exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Generate control is visible only when enabled + reachable', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.getByRole('button', { name: 'Generate (dev)' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Generate text' })).toBeVisible();
|
||||
// With no inference configured, the control is disabled with a plain note.
|
||||
const note = page.locator('.note');
|
||||
const state = page.locator('.state');
|
||||
await expect(state).toContainText(/endpoint: (reachable|unreachable)/);
|
||||
const text = (await state.textContent()) ?? '';
|
||||
if (text.includes('unreachable')) {
|
||||
await expect(note).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user