#!/usr/bin/env lmscript # scripts/test-lmwebtest.lms # # End-to-end QA & integration test suite for the lmscript.io website using: # 1. `lmwebtest` Web DSL (`cli="lmwebtest"`, `context web: Web` & `context report: Report`) # 2. `lmwebtest playwright` (`cli="lmwebtest playwright"`, `context pw: Playwright`) # 3. `lmhtml` Lexbor DOM (`cli="lmhtml"`, `context html: Html`) # # Usage: # # Run locally or in GitHub Actions CI via `./scripts/run-lmwebtest.sh`: # LMW_BASE_URL=http://127.0.0.1:8765 lmscript scripts/test-lmwebtest.lms # # Or against live https://lmscript.io: # LMW_BASE_URL=https://lmscript.io lmscript scripts/test-lmwebtest.lms const BASE_URL = env("LMW_BASE_URL", "http://127.0.0.1:8765"); const OUT_DIR = "test-results/lmwebtest"; mkdir(OUT_DIR); println("================================================================"); println(" lmscript.io — lmctl-ai/lmwebtest QA & Integration Test Suite"); println("================================================================"); println(" Target URL : " + BASE_URL); println(" Output Dir : " + OUT_DIR); println("----------------------------------------------------------------"); # ---------------------------------------------------------------------------- # Stage 1: `lmwebtest` Web DSL (`context web: Web`, `context report: Report`) # ---------------------------------------------------------------------------- println("[Stage 1/3] Running lmwebtest Web DSL navigation, DOM assertions & snapshots..."); web_dsl = _lmcode_ cli="lmwebtest" install="github:lmctl-ai/lmwebtest@v0.1.23"; web.set_viewport(1280, 900); web.set_timeout(15000); let routes = qw( / /about.html /404.html /lmscript/ /lmscript/guide.html /lmscript/stdlib.html /lmscript/examples.html /lmscript/agentic.html /lmscript/comparisons.html /lmscript/cheatsheet.html /lmcode/ /lmcode/tutorial.html /lmcode/language.html /lmcode/hosts.html /lmcode/stdlib.html /lmcode/ecosystem.html /examples/ ); let pages_checked = 0; let code_blocks_total = 0; for (route in routes) { let url = ${BASE_URL} + route; let page = web.navigate(url); let st = page.status(); if (st != 200) { report.add("bad_status", route, st, "Expected HTTP 200 on " + route); } if (!page.has("h1")) { report.add("missing_h1", route, 1, "Expected
blocks)");
println(" -> Saved rendered DOM & screenshots to " + OUT_DIR);
# ----------------------------------------------------------------------------
# Stage 2: `lmwebtest playwright` (`context pw: Playwright`)
# Interactive UI QA tests: Theme Toggle, Tabs, Live Search Filter, Copy Button & API
# ----------------------------------------------------------------------------
println("[Stage 2/3] Running lmwebtest playwright interactive browser & API QA tests...");
pw_dsl = _lmcode_ cli="lmwebtest playwright" install="github:lmctl-ai/lmwebtest@v0.1.23";
let base = ${BASE_URL};
let b = pw.launch("chromium");
let p = b.new_page();
p.set_viewport(1280, 900);
# 1. Homepage: Theme Toggle (Dark -> Light -> Dark) & localStorage persistence
p.goto(base + "/");
let initial_theme = p.get_attribute("html", "data-theme");
p.click("[data-theme-toggle]");
let toggled_theme = p.get_attribute("html", "data-theme");
let toggle_label = p.text_content("[data-theme-toggle]");
# Navigate to /lmcode/ and verify light theme persisted via localStorage
p.goto(base + "/lmcode/");
let persisted_theme = p.get_attribute("html", "data-theme");
# Toggle back to dark
p.click("[data-theme-toggle]");
let restored_theme = p.get_attribute("html", "data-theme");
# 2. Homepage: Interactive 4-Tab Code Switcher
p.goto(base + "/");
let tab1_visible_before = p.is_visible("[data-panel='tab-lmaws']");
let tab2_visible_before = p.is_visible("[data-panel='tab-shop']");
p.click("[data-tab='tab-shop']");
let tab1_visible_after = p.is_visible("[data-panel='tab-lmaws']");
let tab2_visible_after = p.is_visible("[data-panel='tab-shop']");
p.click("[data-tab='tab-data']");
let tab3_visible_after = p.is_visible("[data-panel='tab-data']");
p.click("[data-tab='tab-agent']");
let tab4_visible_after = p.is_visible("[data-panel='tab-agent']");
# 3. Host SDKs page (/lmcode/hosts.html): 7 Language Runtime Tabs
p.goto(base + "/lmcode/hosts.html");
let rt_tabs = qw(rt-cpp rt-rust rt-go rt-java rt-csharp rt-js rt-py);
let rt_verified = 0;
for (tab_id in rt_tabs) {
p.click("[data-tab='" + tab_id + "']");
if (p.is_visible("[data-panel='" + tab_id + "']")) {
rt_verified = rt_verified + 1;
}
}
# 4. Live Search / Filter on /lmscript/examples.html (58 examples + regex_tokenizer)
p.goto(base + "/lmscript/examples.html");
let total_ex_rows = p.locator("#examples-table tbody tr").count();
p.fill("[data-filter-input]", "resumepoint");
let ex_filter_badge = p.text_content("#ex-count");
p.fill("[data-filter-input]", "regex_tokenizer");
let ex_regex_badge = p.text_content("#ex-count");
# 5. Live Search / Filter on /lmscript/stdlib.html (template_extract + regex_find_pos)
p.goto(base + "/lmscript/stdlib.html");
p.fill("[data-filter-input]", "template_extract");
let std_filter_badge = p.text_content("#stdlib-count");
p.fill("[data-filter-input]", "regex_find_pos");
let std_regex_badge = p.text_content("#stdlib-count");
# 6. Live Search / Filter on /lmcode/stdlib.html
p.goto(base + "/lmcode/stdlib.html");
p.fill("[data-filter-input]", "key_at");
let lmc_filter_badge = p.text_content("#lmc-std-count");
# 7. Code Copy Button interaction on /lmscript/guide.html
p.goto(base + "/lmscript/guide.html");
let copy_btn_initial = p.locator(".copy-btn").first().text_content();
# 8. Verify zero page errors and zero failed network requests
let page_errs = p.page_errors();
let failed_reqs = p.failed_requests();
# 9. API Context checks for static assets, SVG diagrams, and downloadable .lms scripts
let api = pw.request_new();
let r_robots = api.get(base + "/robots.txt");
let r_sitemap = api.get(base + "/sitemap.xml");
let r_arch = api.get(base + "/assets/architecture.svg");
let r_dslb = api.get(base + "/assets/dslb-pipeline.svg");
let r_setup = api.get(base + "/examples/lmcode/setup-lmscript-io.lms");
let r_deploy = api.get(base + "/examples/lmcode/deploy-site.lms");
let r_regex = api.get(base + "/examples/lmscript/regex-tokenizer.lms");
let r_sqlite = api.get(base + "/examples/lmscript/sqlite-demo.lms");
$return.initial_theme = initial_theme;
$return.toggled_theme = toggled_theme;
$return.toggle_label = toggle_label;
$return.persisted_theme = persisted_theme;
$return.restored_theme = restored_theme;
$return.tab1_visible_before = tab1_visible_before;
$return.tab2_visible_before = tab2_visible_before;
$return.tab1_visible_after = tab1_visible_after;
$return.tab2_visible_after = tab2_visible_after;
$return.tab3_visible_after = tab3_visible_after;
$return.tab4_visible_after = tab4_visible_after;
$return.rt_verified = rt_verified;
$return.total_ex_rows = total_ex_rows;
$return.ex_filter_badge = ex_filter_badge;
$return.ex_regex_badge = ex_regex_badge;
$return.std_filter_badge = std_filter_badge;
$return.std_regex_badge = std_regex_badge;
$return.lmc_filter_badge = lmc_filter_badge;
$return.copy_btn_initial = copy_btn_initial;
$return.page_errs = page_errs;
$return.failed_reqs = failed_reqs;
$return.robots_status = r_robots.status();
$return.sitemap_status = r_sitemap.status();
$return.arch_svg_ok = str_contains(r_arch.text(), "