#!/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

heading on " + route); } if (!page.has("header.topbar")) { report.add("missing_topbar", route, 1, "Expected header.topbar on " + route); } if (!page.has("footer.site-footer")) { report.add("missing_footer", route, 1, "Expected footer.site-footer on " + route); } pages_checked = pages_checked + 1; code_blocks_total = code_blocks_total + page.count("pre code"); if (route == "/") { page.save_html("test-results/lmwebtest/rendered-home.html"); page.screenshot("test-results/lmwebtest/home.png"); } else if (route == "/lmscript/examples.html") { page.save_html("test-results/lmwebtest/rendered-examples.html"); } else if (route == "/lmcode/language.html") { page.save_html("test-results/lmwebtest/rendered-lmcode-spec.html"); page.screenshot("test-results/lmwebtest/lmcode-spec.png"); } } file_write( "test-results/lmwebtest/web-stats.json", "{\"pages_checked\":" + str(pages_checked) + ",\"code_blocks_total\":" + str(code_blocks_total) + "}\n" ); report.write_json("test-results/lmwebtest/web-report.json"); report.fail_if_any(); web.close(); end _lmcode_ r1 = run_lmcode(web_dsl); require(r1.ok, "Stage 1 (lmwebtest Web DSL) failed"); web_stats = json_parse(read_file("test-results/lmwebtest/web-stats.json")); web_report = json_parse(read_file("test-results/lmwebtest/web-report.json")); assert(web_report.len() == 0, "Expected zero issues in lmwebtest web-report.json"); println(" -> Navigated & verified " + str(web_stats.pages_checked) + " pages (" + str(web_stats.code_blocks_total) + "
 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(), " Theme toggle & localStorage persistence : PASS (dark -> light -> dark)");
println("  -> Homepage 4-tab switcher                 : PASS");
println("  -> Host SDKs 7-runtime tab switcher        : PASS (7/7 runtimes)");
println("  -> Live filter search (examples & stdlib)  : PASS (" + pw_res.ex_filter_badge + ", regex_find_pos: " + pw_res.std_regex_badge + ")");
println("  -> Browser page_errors / failed_requests   : PASS (0 errors, 0 failed requests)");
println("  -> Asset & .lms download API checks        : PASS");

# ----------------------------------------------------------------------------
# Stage 3: `lmhtml` Lexbor DOM Verification on Rendered Snapshots & site/
# ----------------------------------------------------------------------------
println("[Stage 3/3] Running lmhtml Lexbor DOM extraction & markup verification...");

html_dsl = _lmcode_ cli="lmhtml" install="github:lmctl-ai/lmhtml@v0.7.5";
  let home_doc = html.load("test-results/lmwebtest/rendered-home.html");
  let ex_doc   = html.load("test-results/lmwebtest/rendered-examples.html");
  let spec_doc = html.load("test-results/lmwebtest/rendered-lmcode-spec.html");

  let home_cards = home_doc.select(".card");
  let ex_rows    = ex_doc.select("#examples-table tbody tr");
  let spec_ops   = spec_doc.select("#opcodes + .table-wrap tbody tr");

  let all_docs   = html.load_dir("site");
  let doc_count  = all_docs.len();
  let err_count  = 0;

  for (i in range(0, doc_count)) {
    let d    = all_docs.at(i);
    let errs = d.markup_errors();
    err_count = err_count + errs.len();
  }

  lmcode_return_put("home_title",      home_doc.title());
  lmcode_return_put("home_card_count", home_cards.len());
  lmcode_return_put("ex_row_count",    ex_rows.len());
  lmcode_return_put("spec_op_rows",    spec_ops.len());
  lmcode_return_put("site_doc_count",  doc_count);
  lmcode_return_put("markup_errors",   err_count);
end _lmcode_

r3 = run_lmcode(html_dsl);
require(r3.ok, "Stage 3 (lmhtml DOM verification) failed");
h_res = r3.result;

assert(h_res.home_card_count == 14, "Expected 14 cards on homepage");
assert(h_res.ex_row_count == 58, "Expected 58 example rows extracted by lmhtml");
assert(h_res.spec_op_rows == 30, "Expected 30 opcode rows extracted by lmhtml on /lmcode/language.html");
assert(h_res.markup_errors == 0, "Expected 0 HTML markup errors across all site documents");

println("  -> lmhtml audited " + str(h_res.site_doc_count) + " HTML docs (markup_errors=" + str(h_res.markup_errors) + ")");
println("  -> Extracted " + str(h_res.home_card_count) + " homepage cards, " + str(h_res.ex_row_count) + " example rows, " + str(h_res.spec_op_rows) + " opcode table rows");

summary = {
  web_dsl:    web_stats,
  playwright: pw_res,
  lmhtml:     h_res
};
write_file(OUT_DIR + "/summary.json", json_stringify(summary, 2) + "\n");
println("----------------------------------------------------------------");
println(" Wrote full lmwebtest report -> " + OUT_DIR + "/summary.json");
println("================================================================");
println(" ALL LMWEBTEST QA & INTEGRATION CHECKS PASSED");
println("================================================================");