Skip to content

Instantly share code, notes, and snippets.

@825i
Created March 24, 2025 09:39
Show Gist options
  • Save 825i/8d068a1e77cedaf6a02318d255b6a0e8 to your computer and use it in GitHub Desktop.
Save 825i/8d068a1e77cedaf6a02318d255b6a0e8 to your computer and use it in GitHub Desktop.
[Github Actions] - Automatic Workflow Step Expander
// ==UserScript==
// @name GitHub Actions Workflow Step Expander
// @namespace http://tampermonkey.net/
// @version 1.3
// @description Automatically expands all steps in GitHub Actions workflow runs
// @author You
// @match https://github.com/*/*/actions/runs/*
// @match https://github.com/*/*/actions/workflows/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
console.log('GitHub Actions Expander: Script started');
// Counter to limit expansion attempts
let expansionAttempts = 0;
const MAX_ATTEMPTS = 5;
// Flag to track if expansion is in progress
let isExpanding = false;
// Function to check if an element is a gear/options button
function isGearButton(element) {
if (element.tagName === 'SUMMARY') {
return element.classList.contains('Button--iconOnly') ||
element.querySelector('.octicon-gear') !== null;
}
return false;
}
// Function to expand all steps
function expandAllSteps() {
// Avoid running multiple instances simultaneously
if (isExpanding) return;
isExpanding = true;
// Increment attempt counter
expansionAttempts++;
console.log(`GitHub Actions Expander: Expansion attempt ${expansionAttempts}/${MAX_ATTEMPTS}`);
// First, try to find all collapsed details elements
const collapsedDetails = Array.from(document.querySelectorAll('details:not([open])'));
// Filter out the gear buttons and expand the rest
collapsedDetails.forEach(details => {
const summary = details.querySelector('summary');
if (summary && !isGearButton(summary)) {
details.setAttribute('open', 'true');
}
});
// Also look for specifically collapsed step elements
const collapsedSteps = Array.from(document.querySelectorAll('.js-step.collapsed, .ActionList-item.collapsed, .js-workflow-step[aria-expanded="false"]'));
collapsedSteps.forEach(step => {
const expandBtn = step.querySelector('.js-step-expand-collapse-btn') ||
step.querySelector('.ActionList-content') ||
step.querySelector('button[aria-controls]');
if (expandBtn && !expandBtn.closest('summary') && !isGearButton(expandBtn)) {
expandBtn.click();
}
});
// Check if there are still collapsed elements
const stillCollapsed = document.querySelectorAll('details:not([open]):not(.ActionMenu), .js-step.collapsed, .ActionList-item.collapsed');
const collapsedCount = stillCollapsed.length;
// Reset expanding flag
isExpanding = false;
// Only continue if there are still collapsed elements and we haven't exceeded max attempts
if (collapsedCount > 0 && expansionAttempts < MAX_ATTEMPTS) {
console.log(`GitHub Actions Expander: Still found ${collapsedCount} collapsed elements, trying again soon`);
setTimeout(expandAllSteps, 1000);
} else {
console.log('GitHub Actions Expander: Finished expanding elements');
// Reset counter for potential future expansions
expansionAttempts = 0;
// Disconnect observer after a short delay to allow for any final changes
setTimeout(() => {
observer.disconnect();
console.log('GitHub Actions Expander: Observer disconnected');
}, 2000);
}
}
// Initial run with a delay to ensure page is loaded
setTimeout(expandAllSteps, 2000);
// Flag to track navigation
let lastUrl = location.href;
// Handle dynamically loaded content and URL changes
const observer = new MutationObserver((mutations) => {
// Check if URL has changed (user navigated to a different workflow)
if (lastUrl !== location.href) {
lastUrl = location.href;
expansionAttempts = 0; // Reset counter
console.log('GitHub Actions Expander: URL changed, starting expansion');
setTimeout(expandAllSteps, 1000);
return;
}
// Check for significant DOM changes that might indicate new steps loaded
let hasRelevantChanges = false;
for (const mutation of mutations) {
if (mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE &&
(node.querySelector('details') ||
node.querySelector('.js-step') ||
node.classList?.contains('js-step'))) {
hasRelevantChanges = true;
break;
}
}
}
if (hasRelevantChanges) break;
}
// Only run expansion if relevant changes detected and not currently expanding
if (hasRelevantChanges && !isExpanding && expansionAttempts < MAX_ATTEMPTS) {
console.log('GitHub Actions Expander: Detected new workflow elements');
setTimeout(expandAllSteps, 500);
}
});
// Start observing changes to the DOM
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
// Re-attach observer when user navigates
window.addEventListener('popstate', () => {
if (lastUrl !== location.href) {
lastUrl = location.href;
expansionAttempts = 0;
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
setTimeout(expandAllSteps, 1000);
}
});
})();
@825i
Copy link
Author

825i commented Mar 24, 2025

This script automatically expands all workflow stages/steps on a workflow page for Github Actions.

I need this because the "search" bar at the top right does not properly search through all workflow steps for search strings eg. "warning", "error" unless a stage has been expanded. I found it frustrating to expand each of the steps manually as some workflows have many steps. Going from bottom to top was the fastest but still annoying when debugging many workflows in a row.

This script takes care of that issue and expands all stages immediately, allowing me to search the entire workflow for strings.

The script also still allows you to collapse steps if you want after they have been expanded (without expanding them again).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment