Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 50 additions & 14 deletions .github/skills/fix-robot-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,50 @@ argument-hint: 'Optional: path to the output.xml or results folder containing th

## Step 1 — Parse failures from output.xml

Identify the failure types by parsing the output.xml (usually in `results/` or `robot-results*/`):
Locate the `output.xml` — it is usually in `results/` or in an alternate folder such as `robot-results (4)/` when the run came from a downloaded CI artifact.

```python
import xml.etree.ElementTree as ET
tree = ET.parse('results/output.xml')
To diagnose failures, write the script below to a temp file and run it (writing to a file avoids PowerShell quote-escaping issues with inline `python -c`):

```powershell
@'
import glob, os, xml.etree.ElementTree as ET

candidates = glob.glob("results/output.xml") + glob.glob("robot-results*/output.xml")
if not candidates:
raise FileNotFoundError("No output.xml found")
xml_path = max(candidates, key=os.path.getmtime) # most recently modified
print("Parsing: " + xml_path + "\n")

tree = ET.parse(xml_path)
root = tree.getroot()
for test in root.iter('test'):
status = test.find('status')
if status is not None and status.get('status') == 'FAIL':
name = test.get('name')
for msg in test.iter('msg'):
if msg.get('level') in ('FAIL', 'ERROR'):
print(f'FAIL [{name}]: {(msg.text or "")[:400]}')
for test in root.iter("test"):
status = test.find("status")
if status is None or status.get("status") != "FAIL":
continue
name = test.get("name")
all_msgs = [msg.text or "" for msg in test.iter("msg")]
fail_msgs = [msg.text or "" for msg in test.iter("msg") if msg.get("level") in ("FAIL", "ERROR")]
fail_text = next((m for m in all_msgs if "compared images are different" in m.lower()), None)
timeout_text = next((m for m in all_msgs if "timeouterror" in m.lower() or "element is not visible" in m.lower()), None)
if fail_text:
screenshot_msg = next((m for m in all_msgs if "browser/screenshot/" in m), "")
reference_msg = next((m for m in all_msgs if "dashboard_output" in m), "")
screenshot_file = screenshot_msg.split("browser/screenshot/")[-1].split('"')[0].split("'")[0].strip() if screenshot_msg else "?"
ref_path = reference_msg.split("dashboard_output/")[-1].strip() if reference_msg else "?"
ref_folder = ref_path.split("/")[0] if "/" in ref_path else "?"
print("STALE SCREENSHOT [" + name + "]")
print(" screenshot : " + screenshot_file)
print(" ref folder : tests/robot/resources/dashboard_output/" + ref_folder + "/")
elif timeout_text:
element_msg = next((m for m in all_msgs if "locator" in m.lower() or "click" in m.lower()), timeout_text)
print("TIMEOUT / NOT VISIBLE [" + name + "]")
print(" " + element_msg[:300])
else:
other = next((m for m in fail_msgs if m.strip()), "")
print("OTHER FAILURE [" + name + "]: " + other[:300])
print()
'@ | Out-File -Encoding utf8 "$env:TEMP\rf_diag.py"
python "$env:TEMP\rf_diag.py"
```

Two root causes appear in this project:
Expand All @@ -45,15 +76,20 @@ The `Generate Dashboard` keyword (in `general-keywords.resource`) calls `robotda

## Step 3 — Update stale reference screenshots

### Option A — Copy from a previous Docker run
If `results/browser/screenshot/<name>.png` was captured inside Docker (e.g. from a previous `docker run` test run) and the new UI is correct, copy it straight to the reference folder:
### Option A — Copy from a Docker run (local or CI artifact)
If the screenshots came from a Docker run — either a local `docker run` or a downloaded CI artifact folder such as `robot-results (4)/` — and the new UI is correct, copy them straight to the reference folder using the `Copy-Item` commands printed by the Step 1 script, or manually:

```powershell
# screenshots from a local Docker run land in results\browser\screenshot\
Copy-Item "results\browser\screenshot\<name>.png" `
"tests\robot\resources\dashboard_output\<folder>\<name>.png" -Force

# screenshots from a CI artifact folder (e.g. robot-results (4)\) follow the same layout
Copy-Item "robot-results (4)\browser\screenshot\<name>.png" `
"tests\robot\resources\dashboard_output\<folder>\<name>.png" -Force
```

> **Important**: screenshots taken on a Windows host will not match Docker references due to font rendering differences. Only use Option A if the screenshot came from a Docker run.
> **Important**: screenshots taken on a Windows host will not match Docker references due to font rendering differences. Only use Option A if the screenshots came from a Docker or Linux CI run. Check the `${reference}` log message — a path starting with `/__w/` confirms it ran on Linux.

### Option B — Regenerate via Docker (required when no Docker screenshots exist)

Expand Down
3 changes: 3 additions & 0 deletions docs/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,19 @@ The Dashboard is divided into four sections: **Run, Suite, Test, Keyword**. Each
- **Folder Filter (Donut Chart)** – Click on folder donuts to "zoom in" on specific suites. Affects the Suite Statistics and Suite Duration graphs.
- **Suite Selection Dropdown** – Choose a specific suite or all suites.
- **Full Suite Paths Toggle** – When enabled, shows the full suite path instead of only the suite name. Useful when duplicate suite names exist in different folders.
- **Filters affect top graphs Toggle** – When enabled, the active suite and folder filters also apply to the Most Failed and Most Time Consuming graphs. When disabled, those graphs always show data across all suites regardless of the selection.

#### Test Section
- **Suite Filter** – Select one or multiple suites from a dropdown.
- **Suite Paths Toggle** – Same logic as the Suite section; allows distinguishing duplicate suite names.
- **Test Selection Dropdown** – Zoom in on a specific test.
- **Test Tag Dropdown** – Filter tests by tags.
- **Filters affect top graphs Toggle** – When enabled, the active suite, test, and tag filters also apply to the Most Failed, Most Flaky, Most Time Consuming, and Error Messages graphs. When disabled, those graphs always show data across all tests regardless of the selection.

#### Keyword Section
- **Keyword Dropdown** – Select a specific keyword to zoom in on.
- **Library Names Toggle** – Include library names in the keyword selection dropdown.
- **Filters affect top graphs Toggle** – When enabled, the active keyword filter also applies to the Most Failed, Most Time Consuming, and Most Used graphs. When disabled, those graphs always show data across all keywords regardless of the selection.

## Compare Page

Expand Down
103 changes: 98 additions & 5 deletions robotframework_dashboard/js/eventlisteners.js
Original file line number Diff line number Diff line change
Expand Up @@ -824,9 +824,16 @@ function setup_sections_filters() {
update_overview_filter_visibility();
});
document.getElementById("suiteSelectSuites").addEventListener("change", () => {
update_graphs_with_loading(["suiteStatisticsGraph", "suiteDurationGraph"], () => {
const mostGraphIds = settings.switch.sectionFiltersApplySuite
? ["suiteMostFailedGraph", "suiteMostTimeConsumingGraph"]
: [];
update_graphs_with_loading(["suiteStatisticsGraph", "suiteDurationGraph", ...mostGraphIds], () => {
update_suite_duration_graph();
update_suite_statistics_graph();
if (settings.switch.sectionFiltersApplySuite) {
update_suite_most_failed_graph();
update_suite_most_time_consuming_graph();
}
});
});
update_switch_local_storage("switch.suitePathsSuiteSection", settings.switch.suitePathsSuiteSection, true);
Expand All @@ -844,20 +851,44 @@ function setup_sections_filters() {
}
);
});
update_switch_local_storage("switch.sectionFiltersApplySuite", settings.switch.sectionFiltersApplySuite, true);
document.getElementById("switchSectionFiltersApplySuite").addEventListener("change", () => {
settings.switch.sectionFiltersApplySuite = !settings.switch.sectionFiltersApplySuite;
update_switch_local_storage("switch.sectionFiltersApplySuite", settings.switch.sectionFiltersApplySuite);
update_graphs_with_loading(
["suiteMostFailedGraph", "suiteMostTimeConsumingGraph"],
() => {
update_suite_most_failed_graph();
update_suite_most_time_consuming_graph();
}
);
});
document.getElementById("resetSuiteFolder").addEventListener("click", () => {
update_graphs_with_loading(["suiteFolderDonutGraph", "suiteFolderFailDonutGraph", "suiteStatisticsGraph", "suiteDurationGraph"], () => {
update_suite_folder_donut_graph("");
});
});
document.getElementById("suiteSelectTests").addEventListener("change", () => {
const mostGraphIds = settings.switch.sectionFiltersApplyTest
? ["testMessagesGraph", "testMostFlakyGraph", "testRecentMostFlakyGraph",
"testMostFailedGraph", "testRecentMostFailedGraph", "testMostTimeConsumingGraph"]
: [];
update_graphs_with_loading(
["testStatisticsGraph", "testDurationGraph", "testDurationDeviationGraph"],
["testStatisticsGraph", "testDurationGraph", "testDurationDeviationGraph", ...mostGraphIds],
() => {
setup_testtags_in_select();
setup_tests_in_select();
update_test_statistics_graph();
update_test_duration_graph();
update_test_duration_deviation_graph();
if (settings.switch.sectionFiltersApplyTest) {
update_test_messages_graph();
update_test_most_flaky_graph();
update_test_recent_most_flaky_graph();
update_test_most_failed_graph();
update_test_recent_most_failed_graph();
update_test_most_time_consuming_graph();
}
}
);
});
Expand All @@ -883,38 +914,100 @@ function setup_sections_filters() {
}
);
});
update_switch_local_storage("switch.sectionFiltersApplyTest", settings.switch.sectionFiltersApplyTest, true);
document.getElementById("switchSectionFiltersApplyTest").addEventListener("change", () => {
settings.switch.sectionFiltersApplyTest = !settings.switch.sectionFiltersApplyTest;
update_switch_local_storage("switch.sectionFiltersApplyTest", settings.switch.sectionFiltersApplyTest);
update_graphs_with_loading(
["testMessagesGraph", "testMostFlakyGraph", "testRecentMostFlakyGraph",
"testMostFailedGraph", "testRecentMostFailedGraph", "testMostTimeConsumingGraph"],
() => {
update_test_messages_graph();
update_test_most_flaky_graph();
update_test_recent_most_flaky_graph();
update_test_most_failed_graph();
update_test_recent_most_failed_graph();
update_test_most_time_consuming_graph();
}
);
});
document.getElementById("testTagsSelect").addEventListener("change", () => {
const mostGraphIds = settings.switch.sectionFiltersApplyTest
? ["testMessagesGraph", "testMostFlakyGraph", "testRecentMostFlakyGraph",
"testMostFailedGraph", "testRecentMostFailedGraph", "testMostTimeConsumingGraph"]
: [];
update_graphs_with_loading(
["testStatisticsGraph", "testDurationGraph", "testDurationDeviationGraph"],
["testStatisticsGraph", "testDurationGraph", "testDurationDeviationGraph", ...mostGraphIds],
() => {
setup_tests_in_select();
update_test_statistics_graph();
update_test_duration_graph();
update_test_duration_deviation_graph();
if (settings.switch.sectionFiltersApplyTest) {
update_test_messages_graph();
update_test_most_flaky_graph();
update_test_recent_most_flaky_graph();
update_test_most_failed_graph();
update_test_recent_most_failed_graph();
update_test_most_time_consuming_graph();
}
}
);
});
document.getElementById("testSelect").addEventListener("change", () => {
const mostGraphIds = settings.switch.sectionFiltersApplyTest
? ["testMessagesGraph", "testMostFlakyGraph", "testRecentMostFlakyGraph",
"testMostFailedGraph", "testRecentMostFailedGraph", "testMostTimeConsumingGraph"]
: [];
update_graphs_with_loading(
["testStatisticsGraph", "testDurationGraph", "testDurationDeviationGraph"],
["testStatisticsGraph", "testDurationGraph", "testDurationDeviationGraph", ...mostGraphIds],
() => {
update_test_statistics_graph();
update_test_duration_graph();
update_test_duration_deviation_graph();
if (settings.switch.sectionFiltersApplyTest) {
update_test_messages_graph();
update_test_most_flaky_graph();
update_test_recent_most_flaky_graph();
update_test_most_failed_graph();
update_test_recent_most_failed_graph();
update_test_most_time_consuming_graph();
}
}
);
});
document.getElementById("keywordSelect").addEventListener("change", () => {
const mostGraphIds = settings.switch.sectionFiltersApplyKeyword
? ["keywordMostFailedGraph", "keywordMostTimeConsumingGraph", "keywordMostUsedGraph"]
: [];
update_graphs_with_loading(
["keywordStatisticsGraph", "keywordTimesRunGraph", "keywordTotalDurationGraph",
"keywordAverageDurationGraph", "keywordMinDurationGraph", "keywordMaxDurationGraph"],
"keywordAverageDurationGraph", "keywordMinDurationGraph", "keywordMaxDurationGraph", ...mostGraphIds],
() => {
update_keyword_statistics_graph();
update_keyword_times_run_graph();
update_keyword_total_duration_graph();
update_keyword_average_duration_graph();
update_keyword_min_duration_graph();
update_keyword_max_duration_graph();
if (settings.switch.sectionFiltersApplyKeyword) {
update_keyword_most_failed_graph();
update_keyword_most_time_consuming_graph();
update_keyword_most_used_graph();
}
}
);
});
update_switch_local_storage("switch.sectionFiltersApplyKeyword", settings.switch.sectionFiltersApplyKeyword, true);
document.getElementById("switchSectionFiltersApplyKeyword").addEventListener("change", () => {
settings.switch.sectionFiltersApplyKeyword = !settings.switch.sectionFiltersApplyKeyword;
update_switch_local_storage("switch.sectionFiltersApplyKeyword", settings.switch.sectionFiltersApplyKeyword);
update_graphs_with_loading(
["keywordMostFailedGraph", "keywordMostTimeConsumingGraph", "keywordMostUsedGraph"],
() => {
update_keyword_most_failed_graph();
update_keyword_most_time_consuming_graph();
update_keyword_most_used_graph();
}
);
});
Expand Down
18 changes: 15 additions & 3 deletions robotframework_dashboard/js/graph_creation/keyword.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,26 @@ function _build_keyword_average_duration_config() { return _build_keyword_durati
function _build_keyword_min_duration_config() { return _build_keyword_duration_config("keywordMinDuration", "min_time_s", "Duration"); }
function _build_keyword_max_duration_config() { return _build_keyword_duration_config("keywordMaxDuration", "max_time_s", "Duration"); }

function _get_keyword_most_filtered_data() {
if (!settings.switch.sectionFiltersApplyKeyword) return filteredKeywords;
const keywordSelectValue = document.getElementById("keywordSelect").value;
const useLibraryNames = settings?.switch?.useLibraryNames === true;
return filteredKeywords.filter(keyword => {
const keywordKey = useLibraryNames && keyword.owner
? `${keyword.owner}.${keyword.name}`
: keyword.name;
return keywordKey === keywordSelectValue;
});
}

function _build_keyword_most_failed_config() {
return build_most_failed_config("keywordMostFailed", "keyword", "Keyword", filteredKeywords, false);
return build_most_failed_config("keywordMostFailed", "keyword", "Keyword", _get_keyword_most_filtered_data(), false);
}
function _build_keyword_most_time_consuming_config() {
return build_most_time_consuming_config("keywordMostTimeConsuming", "keyword", "Keyword", filteredKeywords, "onlyLastRunKeyword");
return build_most_time_consuming_config("keywordMostTimeConsuming", "keyword", "Keyword", _get_keyword_most_filtered_data(), "onlyLastRunKeyword");
}
function _build_keyword_most_used_config() {
return build_most_time_consuming_config("keywordMostUsed", "keyword", "Keyword", filteredKeywords, "onlyLastRunKeywordMostUsed", "Most Used", true, (info, name) => `${name}: ran ${info.timesRun} times`);
return build_most_time_consuming_config("keywordMostUsed", "keyword", "Keyword", _get_keyword_most_filtered_data(), "onlyLastRunKeywordMostUsed", "Most Used", true, (info, name) => `${name}: ran ${info.timesRun} times`);
}

// create functions
Expand Down
17 changes: 15 additions & 2 deletions robotframework_dashboard/js/graph_creation/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,16 @@ function _build_suite_duration_config() {
return config;
}

function _get_suite_most_filtered_data() {
if (!settings.switch.sectionFiltersApplySuite) return filteredSuites;
return filteredSuites.filter(s => !exclude_from_suite_data("suite", s));
}

function _build_suite_most_failed_config() {
return build_most_failed_config("suiteMostFailed", "suite", "Suite", filteredSuites, false);
return build_most_failed_config("suiteMostFailed", "suite", "Suite", _get_suite_most_filtered_data(), false);
}
function _build_suite_most_time_consuming_config() {
return build_most_time_consuming_config("suiteMostTimeConsuming", "suite", "Suite", filteredSuites, "onlyLastRunSuite");
return build_most_time_consuming_config("suiteMostTimeConsuming", "suite", "Suite", _get_suite_most_filtered_data(), "onlyLastRunSuite");
}

// create functions
Expand All @@ -214,6 +219,10 @@ function create_suite_folder_donut_graph(folder) {
update_suite_folder_fail_donut_graph();
update_suite_statistics_graph();
update_suite_duration_graph();
if (settings.switch.sectionFiltersApplySuite) {
update_suite_most_failed_graph();
update_suite_most_time_consuming_graph();
}
}
if (suiteFolderDonutGraph) { suiteFolderDonutGraph.destroy(); }
suiteFolderDonutGraph = new Chart("suiteFolderDonutGraph", _build_suite_folder_donut_config(folder));
Expand All @@ -233,6 +242,10 @@ function update_suite_folder_donut_graph(folder) {
update_suite_folder_fail_donut_graph();
update_suite_statistics_graph();
update_suite_duration_graph();
if (settings.switch.sectionFiltersApplySuite) {
update_suite_most_failed_graph();
update_suite_most_time_consuming_graph();
}
}
if (!suiteFolderDonutGraph) { create_suite_folder_donut_graph(folder); return; }
const config = _build_suite_folder_donut_config(folder);
Expand Down
Loading
Loading