Skip to main content

lean_ctx_patterns/
playwright.rs

1// SPDX-License-Identifier: Apache-2.0
2
3macro_rules! static_regex {
4    ($pattern:expr_2021) => {{
5        static RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
6        RE.get_or_init(|| {
7            regex::Regex::new($pattern).expect(concat!("BUG: invalid static regex: ", $pattern))
8        })
9    }};
10}
11
12fn pw_failed_re() -> &'static regex::Regex {
13    static_regex!(r"^\s+\d+\)\s+(.+)$")
14}
15
16pub fn compress(command: &str, output: &str) -> Option<String> {
17    if command.contains("cypress") {
18        return Some(compress_cypress(output));
19    }
20    Some(compress_playwright(output))
21}
22
23fn compress_playwright(output: &str) -> String {
24    let trimmed = output.trim();
25    if trimmed.is_empty() {
26        return "ok".to_string();
27    }
28
29    let mut passed = 0u32;
30    let mut failed = 0u32;
31    let mut skipped = 0u32;
32    let mut failed_names = Vec::new();
33    let mut duration = String::new();
34
35    for line in trimmed.lines() {
36        let l = line.trim().to_lowercase();
37        if l.contains("passed")
38            && let Some(n) = extract_number(&l, "passed")
39        {
40            passed = n;
41        }
42        if l.contains("failed")
43            && let Some(n) = extract_number(&l, "failed")
44        {
45            failed = n;
46        }
47        if l.contains("skipped")
48            && let Some(n) = extract_number(&l, "skipped")
49        {
50            skipped = n;
51        }
52        if let Some(caps) = pw_failed_re().captures(line) {
53            failed_names.push(caps[1].trim().to_string());
54        }
55        if l.contains("finished in") || l.contains("duration") {
56            duration = line.trim().to_string();
57        }
58    }
59
60    let total = passed + failed + skipped;
61    if total == 0 {
62        return compact_output(trimmed, 10);
63    }
64
65    let mut parts = Vec::new();
66    parts.push(format!(
67        "{total} tests: {passed} passed, {failed} failed, {skipped} skipped"
68    ));
69
70    if !failed_names.is_empty() {
71        parts.push("failed:".to_string());
72        for name in failed_names.iter().take(10) {
73            parts.push(format!("  {name}"));
74        }
75        if failed_names.len() > 10 {
76            parts.push(format!("  ... +{} more", failed_names.len() - 10));
77        }
78    }
79
80    if !duration.is_empty() {
81        parts.push(duration);
82    }
83
84    parts.join("\n")
85}
86
87fn compress_cypress(output: &str) -> String {
88    let trimmed = output.trim();
89    if trimmed.is_empty() {
90        return "ok".to_string();
91    }
92
93    let mut passed = 0u32;
94    let mut failed = 0u32;
95    let mut pending = 0u32;
96
97    for line in trimmed.lines() {
98        let l = line.trim().to_lowercase();
99        if l.contains("passing") {
100            passed += extract_first_number(&l);
101        }
102        if l.contains("failing") {
103            failed += extract_first_number(&l);
104        }
105        if l.contains("pending") {
106            pending += extract_first_number(&l);
107        }
108    }
109
110    let total = passed + failed + pending;
111    if total == 0 {
112        return compact_output(trimmed, 10);
113    }
114
115    format!("{total} tests: {passed} passed, {failed} failed, {pending} pending")
116}
117
118fn extract_number(line: &str, keyword: &str) -> Option<u32> {
119    let pos = line.find(keyword)?;
120    let before = &line[..pos];
121    before.split_whitespace().last()?.parse().ok()
122}
123
124fn extract_first_number(line: &str) -> u32 {
125    for word in line.split_whitespace() {
126        if let Ok(n) = word.parse::<u32>() {
127            return n;
128        }
129    }
130    0
131}
132
133fn compact_output(text: &str, max: usize) -> String {
134    let lines: Vec<&str> = text.lines().filter(|l| !l.trim().is_empty()).collect();
135    if lines.len() <= max {
136        return lines.join("\n");
137    }
138    format!(
139        "{}\n{}",
140        lines[..max].join("\n"),
141        super::elision_marker(lines.len() - max)
142    )
143}