#!/usr/bin/env python3 """Kitting: statuses, the owner, the filter — CR-009 (T8.1) + CR-010 (T8.2). The statuses become an explicit five (Not Started / Picking / Staged / In Transit / Delivered) - fulfillment states, not free text. A value stored before the set existed is kept and shown as a legacy option: renamed sets must not orphan recorded data. The Micron EUV configuration (the sample SOP, per CR-016/T5.7) turns the section OFF through the CR-006 toggle; any other SOP can turn it on and everything works; nothing is deleted either way. Self-contained: throwaway SQLite, its own uvicorn, headless Edge or Chrome. Exit 0 all passed, 1 a failure, 2 could not run. """ import json import os import re import sys import tempfile import time sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import cdp # noqa: E402 from browser_check import seed, start_server, chk, _PASS, _FAIL # noqa: E402 from sections_check import set_sop # noqa: E402 from stepper_check import dismiss_dialogs # noqa: E402 KIT = ["Not Started", "Picking", "Staged", "In Transit", "Delivered"] def ascii_(v, n=280): return re.sub(r"\s+", " ", str(v)).encode("ascii", "replace").decode()[:n] def settle(seconds=0.5): time.sleep(seconds) def wait_creator(page, tries=40): for _ in range(tries): if page.eval("!!window.wpCreatorReady"): return True time.sleep(0.3) return False def kit_options(page): return json.loads(page.eval( "JSON.stringify([...document.getElementById('wp_kit_status').options]" ".map(o=>o.textContent))")) def main(): exe = cdp.find_browser() if not exe: print("no headless-capable browser found; set WP_BROWSER.") return 2 tmpdir = tempfile.mkdtemp(prefix="wpsuite-kit-") db_path = os.path.join(tmpdir, "check.db") server = None browser = None try: tok = seed(db_path) set_sop(db_path, {}) # sopA: every section ON, kitting included port = cdp.free_port() base = "http://127.0.0.1:%d" % port server = start_server(port, db_path) browser = cdp.Browser(exe) page = browser.page() page.clear_cookies() page.set_cookie("wp_session", tok["root"]) page.viewport(1440, 900) page.goto(base + "/wp-creation-index.html?project=projA") dismiss_dialogs(page) chk("the creator boots", wait_creator(page)) settle(1.6) page.eval("window.alert=()=>{}; window.confirm=()=>false; window.prompt=()=>null;") # ── 1. the set ──────────────────────────────────────────────────────── print("\n1. an explicit set, not free text") opts = kit_options(page) chk("the kitting status control is a select offering exactly the five states", opts == ["—"] + KIT, ascii_(opts)) chk("...and the set is one named constant", json.loads(page.eval("JSON.stringify(KIT_STATUSES)")) == KIT) # a pre-CR-009 value survives as a legacy option, selected page.eval("""loadPackageIntoForm({subject:'legacy kit', type:'Conduit Install', kitStatus:'Kitted', kitOwner:'Paul Coonrod', constraints:[], holds:[]})""") settle(0.8) chk("a stored pre-CR-009 value is kept, selected, and marked legacy", page.eval("document.getElementById('wp_kit_status').value") == "Kitted" and any("legacy" in o for o in kit_options(page)), ascii_(kit_options(page))) chk("...and it round-trips through collect unchanged - nothing re-written", page.eval("collectPackage().kitStatus") == "Kitted") page.eval("document.getElementById('wp_kit_status').value='Staged'") chk("...and picking a new value works from the same control", page.eval("collectPackage().kitStatus") == "Staged") # ── 2. this SOP (kitting ON) has the section, working ───────────────── print("\n2. an SOP with kitting on") vis = page.eval("""(() => { const c=document.getElementById('mimo-card'); return c && !c.hidden && getComputedStyle(c).display !== 'none'; })()""") chk("the Kitting & MIMO section is present and rendered", bool(vis)) chk("...and appears in the section rail", page.eval( "!!document.querySelector(%s)" % json.dumps('.sec-rail-item[data-sec="mimo-card"]'))) # ── 3. Micron EUV (the sample): kitting OFF ─────────────────────────── print("\n3. Micron EUV: off, hidden, not deleted") page.eval("loadSampleSOP()") settle(1.0) chk("the sample IS the Micron configuration and turns kitting off", page.eval("SOP.sections && SOP.sections.kitting === false")) hidden = page.eval("""(() => { const c=document.getElementById('mimo-card'); return c && (c.hidden || getComputedStyle(c).display === 'none'); })()""") chk("the section is gone from the form", bool(hidden)) chk("...and from the rail", page.eval( "!document.querySelector(%s)" % json.dumps('.sec-rail-item[data-sec="mimo-card"]'))) page.eval("loadExample()") settle(1.0) doc = page.eval("(() => { renderPackage(collectPackage()); " "return document.getElementById('pkg-doc').innerHTML; })()") chk("...and from the export", "Kitting & MIMO" not in doc) chk("the example package's kitting DATA is still on the record - hidden, " "never deleted", page.eval("collectPackage().kitStatus") != "" and bool(page.eval("collectPackage().kitOwner")), ascii_( (page.eval("collectPackage().kitStatus"), page.eval("collectPackage().kitOwner")))) # ── 4. CR-010: the warehouse owner (T8.2) ──────────────────────────── print(chr(10) + "4. CR-010: the warehouse owner") page.eval("set_sop_reload = 0") # marker only page.goto(base + "/wp-creation-index.html?project=projA") dismiss_dialogs(page) wait_creator(page) settle(1.6) page.eval("window.alert=()=>{}; window.confirm=()=>false; window.prompt=()=>null;") chk("the warehouse owner is a dropdown of project members, not free text", page.eval("document.getElementById('wp_kit_owner_sel').tagName") == "SELECT" and page.eval("document.getElementById('wp_kit_owner_sel').options.length") > 1, page.eval("document.getElementById('wp_kit_owner_sel').options.length")) page.eval("""(() => { const sel=document.getElementById('wp_kit_owner_sel'); sel.value=[...sel.options].find(o=>o.value==='user_sue').value; sel.onchange.call(sel); })()""") got = json.loads(page.eval( "JSON.stringify({name: collectPackage().kitOwner, id: collectPackage().kitOwnerId})")) chk("picking a member persists BOTH the display name and the account id " "(the id is what CR-011 notifications will read)", got["id"] == "user_sue" and got["name"], ascii_(got)) page.eval("""loadPackageIntoForm({subject:'legacy owner', type:'Conduit Install', kitOwner:'Paul Coonrod', constraints:[], holds:[]})""") settle(0.8) chk("a stored name with NO account renders as a kept '(no account)' option " "- removing someone from the project breaks nothing", page.eval("document.getElementById('wp_kit_owner_sel').value") == "__orphan__" and "no account" in page.eval( "document.getElementById('wp_kit_owner_sel').selectedOptions[0].textContent")) chk("...and the stored name still round-trips", page.eval("collectPackage().kitOwner") == "Paul Coonrod") # the dashboard filter page.eval("""(() => { const mk=(id,num,owner)=>({id, number:num, subject:'kit '+num, type:'Conduit Install', status:'Draft', kitOwner:owner, constraints:[], holds:[], projectId:activeProjectId}); savedPackages.push(mk('wpKA','WP70-KA','Paul Coonrod')); savedPackages.push(mk('wpKB','WP71-KB','Sue')); saveStore(); renderSavedList(); })()""") page.eval("showDashboard()") settle(1.2) page.eval("dashFilter.kitOwner='Paul Coonrod'; dashPage=0; renderDashboard()") settle(0.8) board = page.eval("(document.getElementById('dash-body')||{textContent:''}).textContent") chk("the dashboard filters by warehouse owner - their package shows", "WP70-KA" in board, ascii_(board, 120)) chk("...and the other owner's does not", "WP71-KB" not in board) chk("...through a labelled control on the board", page.eval("!!document.querySelector('[aria-label=%s]')" % json.dumps("Filter by warehouse owner"))) js_errors = [e for e in page.js_errors() if "beforeunload" not in e] chk("no JavaScript errors anywhere in this run", not js_errors, ascii_(js_errors[:2])) finally: if browser is not None: try: browser.close() except Exception: pass if server is not None: try: server.terminate() except Exception: pass print("\n" + "-" * 54) print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL))) for f in _FAIL: print(" - " + f) return 1 if _FAIL else 0 if __name__ == "__main__": sys.exit(main())