Skip to content

Conversation

VietND96
Copy link
Member

@VietND96 VietND96 commented Sep 4, 2025

User description

Thanks for contributing to the Docker-Selenium project!
A PR well described will help maintainers to quickly review and merge it

Before submitting your PR, please check our contributing guidelines, applied for this repository.
Avoid large PRs, help reviewers by making them as simple and short as possible.

Description

Motivation and Context

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

PR Type

Enhancement


Description

  • Automated browser version matrix updates with new script

  • Enhanced CI workflows with parallel builds and streamlined steps

  • Added version sorting and new browser version support

  • Consolidated version fetching into unified make target


Diagram Walkthrough

flowchart LR
  A["fetch_version.py"] --> B["Browser Matrix YAML"]
  C["update_workflow_versions.py"] --> D["Workflow Files"]
  B --> E["Makefile Target"]
  D --> E
  E --> F["CI Workflows"]
  F --> G["Automated Updates"]
Loading

File Walkthrough

Relevant files
Enhancement
2 files
fetch_version.py
Enhanced version fetching with sorting and new version support
+22/-2   
update_workflow_versions.py
New script to automatically update workflow browser versions
+123/-0 
Configuration changes
8 files
deploy.yml
Added browser version matrix update step                                 
+1/-0     
release-chrome-versions.yml
Updated default versions and added parallel builds             
+4/-11   
release-edge-versions.yml
Added parallel builds and streamlined version fetching     
+3/-10   
release-firefox-versions.yml
Updated default versions and streamlined build process     
+4/-12   
Makefile
Added unified browser version update targets and formatting
+14/-3   
multiple-nodes-platform-version.yaml
Added Chrome and Edge version 139 configurations                 
+10/-0   
browser-matrix.yml
Updated browser versions with Firefox 142 and Chrome 140 
+6/-3     
firefox-matrix.yml
Added Firefox version 142 support                                               
+2/-0     
Documentation
1 files
README.md
Added documentation for browser version matrix updates     
+6/-0     
Dependencies
1 files
requirements.txt
Added Python formatting dependencies                                         
+2/-0     

Copy link
Contributor

qodo-merge-pro bot commented Sep 4, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

Casting browser version keys to int when comparing or sorting may raise ValueError if any key is non-numeric (e.g., 'beta', 'lts', or dotted versions). Consider robust parsing or guarding to avoid crashes and ensure stable ordering.

    local_versions = local_data['matrix']['browser'].keys()
    local_min_version = str(min(int(v) for v in local_versions)) if local_versions else "0"
    for version, details in source_data['matrix']['browser'].items():
        if version in local_data['matrix']['browser']:
            original_details = local_data['matrix']['browser'][version]
            for key in details:
                if key in original_details and '_PACKAGE_' not in key:
                    original_details[key] = details[key] if details[key] is not None else ""
                    updated = True
                elif '_PACKAGE_' not in key:
                    original_details[key] = details[key] if details[key] is not None else ""
                    updated = True
            merge_dicts(original_details, details)
        else:
            if int(version) > int(local_min_version):
                local_data['matrix']['browser'][version] = details
                local_data['matrix']['browser'][version]['FIREFOX_PLATFORMS'] = 'linux/amd64,linux/arm64'
                updated = True
    return updated


def sort_keys(local_data):
    # Sort local_data by key in matrix.browser
    list_versions = list(local_data['matrix']['browser'].keys())
    list_versions.sort(key=lambda x: int(x), reverse=True)
    sorted_browser_dict = {}
    for version in list_versions:
        sorted_browser_dict[version] = local_data['matrix']['browser'][version]
    local_data['matrix']['browser'] = sorted_browser_dict
Fragile Parsing

Workflow updater relies on regex line-by-line edits and sets default as a quoted string of a list, which may not remain valid YAML or intended type. Consider YAML-aware editing and ensuring the resulting type matches the workflow input expectations.

def update_workflow_file(workflow_file, versions_list):
    """Update the workflow file with new version list for browser-versions.default only."""
    with open(workflow_file, 'r') as f:
        lines = f.readlines()

    updated_lines = []
    in_browser_versions = False

    for i, line in enumerate(lines):
        # Check if we're in the browser-versions section
        if re.match(r'^(\s*)browser-versions:\s*$', line):
            in_browser_versions = True
            updated_lines.append(line)
        elif in_browser_versions and re.match(r'^(\s*)default:\s*', line):
            # We found the default line within browser-versions section
            indent_match = re.match(r'^(\s*)default:', line)
            indent = indent_match.group(1) if indent_match else ''
            # Replace the line with new version list
            updated_lines.append(f"{indent}default: '{versions_list}'\n")
            in_browser_versions = False  # Reset flag after updating
        elif in_browser_versions and re.match(r'^(\s+)(description|required|type):\s*', line):
            # Still within browser-versions section, continue
            updated_lines.append(line)
        elif in_browser_versions and re.match(r'^(\s*)[a-zA-Z-]+:\s*', line) and not re.match(r'^(\s+)', line):
            # We've moved to another top-level field, reset the flag
            in_browser_versions = False
            updated_lines.append(line)
        else:
            updated_lines.append(line)

    with open(workflow_file, 'w') as f:
        f.writelines(updated_lines)
Env Impact

Using --break-system-packages in pip install and adding formatting tools to test requirements may affect CI environments or local systems unexpectedly. Validate reproducibility and isolation (e.g., venv) and that lint tooling in tests requirements is intentional.

install_python_deps:
	python3 -m pip install -r tests/requirements.txt --break-system-packages

format_python_scripts: install_python_deps
	python3 -m isort tests/ ; \
    python3 -m black --line-length=120 --skip-string-normalization tests/

generate_readme_charts:
	if [ ! -f $$HOME/go/bin/helm-docs ] ; then \
		echo "helm-docs is not installed. Please install it or run 'make setup_dev_env' once." ; \
	else \
		$$HOME/go/bin/helm-docs --chart-search-root charts/selenium-grid --output-file CONFIGURATION.md --sort-values-order file ; \
	fi

update_list_env_vars: install_python_deps
	python3 scripts/generate_list_env_vars/extract_env.py

update_selenium_version_matrix: install_python_deps
	python3 tests/build-backward-compatible/add_selenium_version.py $(BASE_VERSION)

update_browser_versions_matrix: update_selenium_version_matrix
	python3 tests/build-backward-compatible/fetch_firefox_version.py ; \
	python3 tests/build-backward-compatible/fetch_version.py ; \
	python3 tests/build-backward-compatible/update_workflow_versions.py

Copy link
Contributor

qodo-merge-pro bot commented Sep 4, 2025

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Node/Standalone Firefox (113)

Failed stage: Set up containerd image store feature [❌]

Failure summary:

The action failed while running the nick-invision/retry@master step executing the command make
setup_dev_env. During package installation/setup, the retry action's Node.js process encountered an
unhandled exception: Error: kill EPERM (from
/home/runner/work/_actions/nick-invision/retry/master/dist/index.js:1931, stack at process.kill
(node:internal/process/per_thread:225:13)).
- This indicates the retry action attempted to send a
signal (kill) to a process but lacked permission or the process was not in a state that allowed
signaling, causing the step to crash.
- The failure occurred during the setup phase (APT packages
being fetched), not due to a specific test failure.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

544:  [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
545:  [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader
546:  http.https://github.com/.extraheader
547:  [command]/usr/bin/git config --local --unset-all http.https://github.com/.extraheader
548:  [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :"
549:  ##[endgroup]
550:  ##[group]Run nick-invision/retry@master
551:  with:
552:  timeout_minutes: 10
553:  max_attempts: 3
554:  command: make setup_dev_env
555:  
556:  retry_wait_seconds: 10
557:  polling_interval_seconds: 1
558:  warning_on_retry: true
559:  continue_on_error: false
560:  env:
...

675:  Get:9 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libgcc-s1-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [61.9 kB]
676:  Get:10 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libgomp1-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [110 kB]
677:  Get:11 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libitm1-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [31.5 kB]
678:  Get:12 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libatomic1-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [11.4 kB]
679:  Get:13 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libasan8-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [524 kB]
680:  Get:14 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 liblsan0-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [238 kB]
681:  Get:15 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libtsan2-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [500 kB]
682:  Get:16 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libstdc++6-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [658 kB]
683:  Get:17 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libubsan1-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [219 kB]
684:  Get:18 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libhwasan0-arm64-cross all 14.2.0-4ubuntu2~24.04cross1 [303 kB]
685:  Get:19 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 libgcc-13-dev-arm64-cross all 13.3.0-6ubuntu2~24.04cross1 [2616 kB]
686:  Get:20 http://azure.archive.ubuntu.com/ubuntu noble-updates/main amd64 gcc-13-aarch64-linux-gnu amd64 13.3.0-6ubuntu2~24.04cross1 [21.1 MB]
687:  /home/runner/work/_actions/nick-invision/retry/master/dist/index.js:1931
688:  throw err;
689:  ^
690:  Error: kill EPERM
691:  at process.kill (node:internal/process/per_thread:225:13)

Copy link
Contributor

qodo-merge-pro bot commented Sep 4, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Eliminate untrusted runtime version sources

The workflows now fetch browser versions at build time from personal GitHub
repos (NDViet/*) and mutate matrices/workflows on-the-fly, introducing a
significant supply-chain and reproducibility risk. Source versions from trusted,
official endpoints (e.g., OmahaProxy/Chrome, Edge release feeds, Mozilla product
APIs) or maintain pinned data in-repo updated via a scheduled PR, with strict
schema validation and sanity checks; avoid modifying workflow files during CI
runs. This ensures deterministic builds, reduces network fragility, and
mitigates the risk of arbitrary upstream changes impacting releases.

Examples:

tests/build-backward-compatible/fetch_version.py [5-6]
chrome_url = 'https://raw.githubusercontent.com/NDViet/google-chrome-stable/refs/heads/main/browser-matrix.yml'
edge_url = 'https://raw.githubusercontent.com/NDViet/microsoft-edge-stable/refs/heads/main/browser-matrix.yml'
.github/workflows/release-chrome-versions.yml [122-123]
            make update_browser_versions_matrix
            ./tests/build-backward-compatible/bootstrap.sh ${GRID_VERSION} ${BROWSER_VERSION} ${BROWSER_NAME} ${REUSE_BASE}

Solution Walkthrough:

Before:

# tests/build-backward-compatible/fetch_version.py
chrome_url = 'https://raw.githubusercontent.com/NDViet/google-chrome-stable/...'
edge_url = 'https://raw.githubusercontent.com/NDViet/microsoft-edge-stable/...'

def main():
    # Fetch data from chrome_url and edge_url
    # Update local browser-matrix.yml
    # ...

# .github/workflows/release-chrome-versions.yml
jobs:
  build-release-images:
    steps:
      - name: Build images...
        command: |
          make update_browser_versions_matrix
          ./tests/build-backward-compatible/bootstrap.sh ...

After:

# scripts/update_versions.py (hypothetical)
CHROME_API = "https://omahaproxy.appspot.com/all.json"
EDGE_API = "..." # Official Edge release API

def fetch_official_versions():
    # Fetch latest versions from official APIs
    ...

def update_matrix_file(versions):
    # Update browser-matrix.yml with new versions
    ...

# .github/workflows/update-browser-versions.yml (hypothetical scheduled workflow)
on:
  schedule:
    - cron: '0 0 * * *'
jobs:
  update-versions:
    steps:
      - uses: actions/checkout@v4
      - run: python scripts/update_versions.py
      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v6
Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical supply-chain security risk by fetching browser versions from an untrusted personal GitHub repository (NDViet/*) at runtime, which is a major design flaw.

High
General
Validate version number conversion

The function assumes all version keys can be converted to integers, but this
could fail if non-numeric versions exist. Add validation to handle potential
conversion errors gracefully.

tests/build-backward-compatible/update_workflow_versions.py [7-32]

 def read_browser_matrix(file_path):
     """Read the browser matrix YAML file and extract browser versions."""
     with open(file_path, 'r') as f:
         data = yaml.safe_load(f)
     
     chrome_versions = []
     firefox_versions = []
     edge_versions = []
     
     browsers = data.get('matrix', {}).get('browser', {})
     
     for version, details in browsers.items():
+        try:
+            version_int = int(version)
+        except ValueError:
+            print(f"Skipping non-numeric version: {version}")
+            continue
+            
         # Check for Chrome versions (not null or empty)
         chrome_version = details.get('CHROME_VERSION')
         if chrome_version and chrome_version != 'null' and str(chrome_version).strip():
-            chrome_versions.append(int(version))
+            chrome_versions.append(version_int)
         
         # Check for Firefox versions (not null or empty)
         firefox_version = details.get('FIREFOX_VERSION')
         if firefox_version and firefox_version != 'null' and str(firefox_version).strip():
-            firefox_versions.append(int(version))
+            firefox_versions.append(version_int)
         
         # Check for Edge versions (not null or empty)
         edge_version = details.get('EDGE_VERSION')
         if edge_version and edge_version != 'null' and str(edge_version).strip():
-            edge_versions.append(int(version))
+            edge_versions.append(version_int)
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential ValueError if version keys in the YAML file are non-numeric, and adding a try-except block makes the script more robust against data format variations.

Low
Handle non-numeric version keys safely

The function assumes all version keys can be converted to integers without
validation. This could cause crashes if non-numeric versions exist in the data.

tests/build-backward-compatible/fetch_version.py [31-51]

 def update_local_yaml(local_data, source_data):
     updated = False
     local_versions = local_data['matrix']['browser'].keys()
-    local_min_version = str(min(int(v) for v in local_versions)) if local_versions else "0"
+    try:
+        local_min_version = str(min(int(v) for v in local_versions if v.isdigit())) if local_versions else "0"
+    except ValueError:
+        local_min_version = "0"
+        
     for version, details in source_data['matrix']['browser'].items():
         if version in local_data['matrix']['browser']:
             original_details = local_data['matrix']['browser'][version]
             for key in details:
                 if key in original_details and '_PACKAGE_' not in key:
                     original_details[key] = details[key] if details[key] is not None else ""
                     updated = True
                 elif '_PACKAGE_' not in key:
                     original_details[key] = details[key] if details[key] is not None else ""
                     updated = True
             merge_dicts(original_details, details)
         else:
-            if int(version) > int(local_min_version):
-                local_data['matrix']['browser'][version] = details
-                local_data['matrix']['browser'][version]['FIREFOX_PLATFORMS'] = 'linux/amd64,linux/arm64'
-                updated = True
+            try:
+                if int(version) > int(local_min_version):
+                    local_data['matrix']['browser'][version] = details
+                    local_data['matrix']['browser'][version]['FIREFOX_PLATFORMS'] = 'linux/amd64,linux/arm64'
+                    updated = True
+            except ValueError:
+                print(f"Skipping non-numeric version: {version}")
+                continue
     return updated
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that converting version keys to integers without validation can cause a crash, and the proposed change with try-except blocks makes the script more robust.

Low
Add file operation error handling

The function doesn't handle the case where a file might not be found or have
permission issues. Add proper error handling to prevent the script from crashing
when file operations fail.

tests/build-backward-compatible/update_workflow_versions.py [55-86]

 def update_workflow_file(workflow_file, versions_list):
     """Update the workflow file with new version list for browser-versions.default only."""
-    with open(workflow_file, 'r') as f:
-        lines = f.readlines()
+    try:
+        with open(workflow_file, 'r') as f:
+            lines = f.readlines()
+    except (FileNotFoundError, PermissionError) as e:
+        print(f"Error reading {workflow_file}: {e}")
+        return
     
     updated_lines = []
     in_browser_versions = False
     
     for i, line in enumerate(lines):
         # Check if we're in the browser-versions section
         if re.match(r'^(\s*)browser-versions:\s*$', line):
             in_browser_versions = True
             updated_lines.append(line)
         elif in_browser_versions and re.match(r'^(\s*)default:\s*', line):
             # We found the default line within browser-versions section
             indent_match = re.match(r'^(\s*)default:', line)
             indent = indent_match.group(1) if indent_match else ''
             # Replace the line with new version list
             updated_lines.append(f"{indent}default: '{versions_list}'\n")
             in_browser_versions = False  # Reset flag after updating
         elif in_browser_versions and re.match(r'^(\s+)(description|required|type):\s*', line):
             # Still within browser-versions section, continue
             updated_lines.append(line)
         elif in_browser_versions and re.match(r'^(\s*)[a-zA-Z-]+:\s*', line) and not re.match(r'^(\s+)', line):
             # We've moved to another top-level field, reset the flag
             in_browser_versions = False
             updated_lines.append(line)
         else:
             updated_lines.append(line)
     
-    with open(workflow_file, 'w') as f:
-        f.writelines(updated_lines)
+    try:
+        with open(workflow_file, 'w') as f:
+            f.writelines(updated_lines)
+    except PermissionError as e:
+        print(f"Error writing to {workflow_file}: {e}")
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out missing error handling for file operations, which improves the script's robustness, although the risk of such errors is low in the CI context where it runs.

Low
  • Update

@VietND96 VietND96 force-pushed the update-matrix-version branch from 1738056 to 2955b68 Compare September 4, 2025 16:45
@VietND96 VietND96 force-pushed the update-matrix-version branch from 2955b68 to 7cee63e Compare September 4, 2025 17:29
@VietND96 VietND96 merged commit ea46846 into trunk Sep 4, 2025
56 of 71 checks passed
@VietND96 VietND96 deleted the update-matrix-version branch September 4, 2025 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant