mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
The config held literals for values vars/main.yml already owned, and the two drifted without a sound. actionTimeout carried `|| 30_000` for its whole life while nothing ever set PLAYWRIGHT_ACTION_TIMEOUT, so every run used the unscaled 30 s. expect had no entry at all, leaving Playwright's built-in 5 s to govern every assertion over Tor while its neighbours were scaled by the onion multiplier. That is what killed the second Nextcloud login attempt: an expect(usernameField).toBeVisible() that gave up after 5 s inside a 150 s budget. vars/main.yml now owns the values in TEST_E2E_PLAYWRIGHT_CONFIG_TIMEOUTS, the harness renders that map into the staged .env both the deploy and scripts/tests/e2e/rerun-spec.sh already pass, and the config demands each name through requiredTimeout() instead of inventing a fallback. The two `-e` flags that fed the old pair are gone with it. scripts/lint/playwright.sh stages the same config and runs `playwright test --list`, which loads it, but built its stub env only from each role's playwright.env.j2 and no such template carries a timeout. Without this it would have aborted all 87 specs on a missing variable. It now takes the names from the same map; the value is a placeholder because --list consumes none of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
288 lines
11 KiB
YAML
288 lines
11 KiB
YAML
---
|
|
- name: 📌 Define per-role paths
|
|
ansible.builtin.set_fact:
|
|
_playwright_env_template: "{{ lookup('path_absolute', 'roles', application_id, 'templates/playwright.env.j2') }}"
|
|
_playwright_files_dir: "{{ lookup('path_absolute', 'roles', application_id, 'files/playwright') }}"
|
|
_playwright_spec_src: "{{ lookup('path_absolute', 'roles', application_id, 'files/playwright/playwright.spec.js') }}"
|
|
_playwright_stage_dir: "{{ TEST_E2E_PLAYWRIGHT_STAGE_BASE_DIR }}/{{ application_id }}"
|
|
_playwright_stage_tests_dir: "{{ TEST_E2E_PLAYWRIGHT_STAGE_BASE_DIR }}/{{ application_id }}/tests"
|
|
_playwright_reports_dir: "{{ TEST_E2E_PLAYWRIGHT_REPORTS_BASE_DIR }}/{{ application_id }}"
|
|
_playwright_default_package_template: "{{ lookup('path_absolute', 'roles/test-e2e-playwright/templates/package.json.j2') }}"
|
|
_playwright_default_config_js: "{{ lookup('path_absolute', 'roles/test-e2e-playwright/files/playwright.config.js') }}"
|
|
_playwright_ca_required: "{{ (TLS_MODE | lower) == 'self_signed' }}"
|
|
_playwright_ca_cert_host: "{{ CA_TRUST.cert_host }}"
|
|
_playwright_ca_wrapper_host: "{{ CA_TRUST.wrapper_host }}"
|
|
_playwright_ca_trust_name: "{{ CA_TRUST.trust_name }}"
|
|
|
|
- name: ✅ Validate CA_TRUST configuration for self-signed TLS
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
TLS_MODE=self_signed requires CA_TRUST.cert_host, CA_TRUST.wrapper_host and CA_TRUST.trust_name
|
|
to inject trust into the Playwright container.
|
|
when: >-
|
|
(_playwright_ca_required | bool) and
|
|
(
|
|
(_playwright_ca_cert_host | length == 0) or
|
|
(_playwright_ca_wrapper_host | length == 0) or
|
|
(_playwright_ca_trust_name | length == 0)
|
|
)
|
|
|
|
- name: 🔎 Check CA certificate for Playwright injection
|
|
ansible.builtin.stat:
|
|
path: "{{ _playwright_ca_cert_host }}"
|
|
register: _playwright_ca_cert_stat
|
|
when: _playwright_ca_required | bool
|
|
|
|
- name: 🔎 Check CA wrapper for Playwright injection
|
|
ansible.builtin.stat:
|
|
path: "{{ _playwright_ca_wrapper_host }}"
|
|
register: _playwright_ca_wrapper_stat
|
|
when: _playwright_ca_required | bool
|
|
|
|
- name: 🛑 Fail if CA certificate is missing on host
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
TLS_MODE=self_signed requires CA injection, but certificate is missing:
|
|
{{ _playwright_ca_cert_host }}
|
|
when: (_playwright_ca_required | bool) and not (_playwright_ca_cert_stat.stat.exists | default(false))
|
|
|
|
- name: 🛑 Fail if CA wrapper script is missing on host
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
TLS_MODE=self_signed requires CA injection, but wrapper script is missing:
|
|
{{ _playwright_ca_wrapper_host }}
|
|
when: (_playwright_ca_required | bool) and not (_playwright_ca_wrapper_stat.stat.exists | default(false))
|
|
|
|
- name: ✅ Assert required Playwright env template exists
|
|
ansible.builtin.stat:
|
|
path: "{{ _playwright_env_template }}"
|
|
register: _env_template_stat
|
|
delegate_to: localhost
|
|
|
|
- name: 🛑 Fail if Playwright env template is missing
|
|
ansible.builtin.fail:
|
|
msg: "Missing Playwright env template: {{ _playwright_env_template }}"
|
|
when: not _env_template_stat.stat.exists
|
|
|
|
- name: ✅ Assert required Playwright spec exists
|
|
ansible.builtin.stat:
|
|
path: "{{ _playwright_spec_src }}"
|
|
register: _spec_stat
|
|
delegate_to: localhost
|
|
|
|
- name: 🛑 Fail if Playwright spec is missing
|
|
ansible.builtin.fail:
|
|
msg: "Missing Playwright spec file: {{ _playwright_spec_src }}"
|
|
when: not _spec_stat.stat.exists
|
|
|
|
- name: 🔎 Check optional application vars file
|
|
ansible.builtin.stat:
|
|
path: "{{ lookup('path_absolute', 'roles', application_id, 'vars/main.yml') }}"
|
|
register: _playwright_app_vars_stat
|
|
delegate_to: localhost
|
|
|
|
- name: ⚙️ Load optional application vars
|
|
ansible.builtin.include_vars:
|
|
file: "{{ lookup('path_absolute', 'roles', application_id, 'vars/main.yml') }}"
|
|
when: _playwright_app_vars_stat.stat.exists
|
|
|
|
- name: 📁 Recreate staging directory
|
|
ansible.builtin.file:
|
|
path: "{{ _playwright_stage_dir }}"
|
|
state: absent
|
|
|
|
- name: 📁 Create staging directory
|
|
ansible.builtin.file:
|
|
path: "{{ _playwright_stage_dir }}"
|
|
state: directory
|
|
mode: "0755"
|
|
|
|
- name: 📁 Create staging tests directory
|
|
ansible.builtin.file:
|
|
path: "{{ _playwright_stage_tests_dir }}"
|
|
state: directory
|
|
mode: "0755"
|
|
|
|
- name: 🔎 Check central default package.json template
|
|
ansible.builtin.stat:
|
|
path: "{{ _playwright_default_package_template }}"
|
|
register: _default_package_stat
|
|
delegate_to: localhost
|
|
|
|
- name: 🛑 Fail when central default package.json template is missing
|
|
ansible.builtin.fail:
|
|
msg: "Missing central Playwright package.json template: {{ _playwright_default_package_template }}"
|
|
when: not _default_package_stat.stat.exists
|
|
|
|
- name: 📄 Render package.json from template
|
|
ansible.builtin.template: # nocheck: stack-host-template (per-host playwright stage dir, runs on every test host)
|
|
src: "{{ _playwright_default_package_template }}"
|
|
dest: "{{ _playwright_stage_dir }}/package.json"
|
|
mode: "0644"
|
|
|
|
- name: 🔎 Check central default playwright.config.js
|
|
ansible.builtin.stat:
|
|
path: "{{ _playwright_default_config_js }}"
|
|
register: _default_config_stat
|
|
delegate_to: localhost
|
|
|
|
- name: 🛑 Fail when central default playwright.config.js is missing
|
|
ansible.builtin.fail:
|
|
msg: "Missing central Playwright config: {{ _playwright_default_config_js }}"
|
|
when: not _default_config_stat.stat.exists
|
|
|
|
- name: 📋 Copy central default playwright.config.js
|
|
ansible.builtin.copy:
|
|
src: "{{ _playwright_default_config_js }}"
|
|
dest: "{{ _playwright_stage_dir }}/playwright.config.js"
|
|
mode: "0644"
|
|
|
|
- name: 📋 Copy Playwright tree (spec + companions + addons/ + fixtures/) to staging
|
|
ansible.builtin.copy:
|
|
src: "{{ _playwright_files_dir }}/"
|
|
dest: "{{ _playwright_stage_tests_dir }}/"
|
|
mode: "0644"
|
|
directory_mode: "0755"
|
|
|
|
- name: 📋 Copy shared Playwright helpers into staging tests directory
|
|
ansible.builtin.copy:
|
|
src: "{{ item }}"
|
|
dest: "{{ _playwright_stage_tests_dir }}/{{ item | basename }}"
|
|
mode: "0644"
|
|
when: (item | basename) != 'playwright.config.js'
|
|
with_fileglob:
|
|
- "{{ lookup('path_absolute', 'roles/test-e2e-playwright/files') }}/*.js"
|
|
|
|
- name: ✅ Ensure staging volume directory exists
|
|
ansible.builtin.file:
|
|
path: "{{ _playwright_stage_dir }}/volume"
|
|
state: directory
|
|
mode: "0755"
|
|
|
|
- name: 📄 Render .env from templates/playwright.env.j2
|
|
ansible.builtin.copy:
|
|
dest: "{{ _playwright_stage_dir }}/.env"
|
|
mode: "0644"
|
|
content: |
|
|
{{ lookup('template', _playwright_env_template) }}
|
|
{% for name, value in TEST_E2E_PLAYWRIGHT_CONFIG_TIMEOUTS.items() %}
|
|
{{ name }}={{ value }}
|
|
{% endfor %}
|
|
vars:
|
|
application_id: "{{ application_id }}"
|
|
domains: "{{ lookup('domains') }}"
|
|
|
|
- name: ⏳ Wait until application is ready (any non-error response)
|
|
uri_retry:
|
|
url: "{{ lookup('tls', application_id, 'url.base') }}"
|
|
status_code: "{{ range(100, 500) | list }}"
|
|
validate_certs: false
|
|
follow_redirects: none
|
|
register: _wait_result
|
|
retries: "{{ TEST_E2E_PLAYWRIGHT_WAIT_RETRIES }}"
|
|
delay: "{{ TEST_E2E_PLAYWRIGHT_WAIT_DELAY }}"
|
|
until: >-
|
|
_wait_result.status is defined and
|
|
_wait_result.status | int >= 100 and
|
|
_wait_result.status | int < 500
|
|
when:
|
|
- TEST_E2E_PLAYWRIGHT_WAIT_ENABLED | bool
|
|
- not (lookup('tls', application_id, 'url.base') is search('\.onion'))
|
|
- lookup('config', application_id, 'services.frontend.enabled', true) | bool
|
|
|
|
- name: ✅ Ensure reports dir exists
|
|
ansible.builtin.file:
|
|
path: "{{ _playwright_reports_dir }}"
|
|
state: directory
|
|
mode: "0755"
|
|
|
|
- name: 📊 Collect host disk diagnostics before Playwright
|
|
ansible.builtin.script: "{{ role_path }}/files/dump_disk_diagnostics.sh"
|
|
changed_when: false
|
|
when: MODE_DEBUG | bool
|
|
|
|
- name: ⏳ Wait until the onion answers over Tor (recreate circuits on exhaustion)
|
|
ansible.builtin.script: >-
|
|
{{ role_path }}/files/wait_onion_reachable.sh
|
|
{{ lookup('tor_socks') | quote }}
|
|
{{ lookup('domain', application_id) | quote }}
|
|
{{ TEST_E2E_PLAYWRIGHT_ONION_PROBE_RETRIES | int }}
|
|
{{ TEST_E2E_PLAYWRIGHT_ONION_PROBE_DELAY | int }}
|
|
{{ lookup('container', 'svc-net-tor', 'directories.instance') | quote }}
|
|
changed_when: false
|
|
become: true
|
|
when: TEST_E2E_PLAYWRIGHT_IS_ONION | bool
|
|
|
|
- name: ▶️ Run Playwright container
|
|
ansible.builtin.command:
|
|
argv: >-
|
|
{{
|
|
['container', 'run', '--rm', '--ipc=host', '--shm-size=1g']
|
|
+ (
|
|
['--network', 'host']
|
|
if DEPLOYMENT_MODE == 'swarm'
|
|
else (
|
|
['--add-host=host.docker.internal:host-gateway']
|
|
+ (
|
|
['--add-host=' ~ lookup('tls', 'web-app-keycloak', 'domain') ~ ':host-gateway']
|
|
if 'web-app-keycloak' in group_names
|
|
else []
|
|
)
|
|
)
|
|
)
|
|
+ ['--env-file', _playwright_stage_dir ~ '/.env']
|
|
+ ['-e', 'INFINITO_PLAYWRIGHT_KEEP=' ~ test_e2e_playwright_keep]
|
|
+ ['-e', 'INFINITO_PLAYWRIGHT_GLOBAL_TIMEOUT_MS=' ~ TEST_E2E_PLAYWRIGHT_GLOBAL_TIMEOUT_MS]
|
|
+ ['-e', 'PLAYWRIGHT_PROXY=' ~ TEST_E2E_PLAYWRIGHT_PROXY]
|
|
+ ['-e', 'PLAYWRIGHT_WORKERS=' ~ test_e2e_playwright_workers]
|
|
+ ['-e', 'PLAYWRIGHT_FULLY_PARALLEL=' ~ TEST_E2E_PLAYWRIGHT_FULLY_PARALLEL]
|
|
+ ['-e', 'PLAYWRIGHT_TIMEOUT_FACTOR=' ~ TIMEOUT_FACTOR]
|
|
+ (['-e', 'PLAYWRIGHT_ONION_TIMEOUT_MULTIPLIER=' ~ TEST_E2E_PLAYWRIGHT_ONION_TIMEOUT_MULTIPLIER] if (TEST_E2E_PLAYWRIGHT_IS_ONION | bool) else [])
|
|
+ ['-v', _playwright_stage_dir ~ ':/e2e']
|
|
+ ['-v', _playwright_stage_dir ~ '/volume:/volume']
|
|
+ ['-v', _playwright_reports_dir ~ ':/reports']
|
|
+ ['-v', TEST_E2E_PLAYWRIGHT_SHARED_NODE_MODULES ~ ':/e2e/node_modules:ro']
|
|
+ ['-v', TEST_E2E_PLAYWRIGHT_SHARED_PERSONAS ~ ':/e2e/tests/personas:ro']
|
|
+ ['-w', '/e2e']
|
|
+ [TEST_E2E_PLAYWRIGHT_IMAGE]
|
|
+ ['/bin/bash', '-lc', TEST_E2E_PLAYWRIGHT_COMMAND]
|
|
}}
|
|
timeout: "{{ lookup('timeout', TEST_E2E_PLAYWRIGHT_RUN_TIMEOUT_EFFECTIVE) | int }}"
|
|
environment: >-
|
|
{{
|
|
{
|
|
'CA_TRUST_CERT_HOST': _playwright_ca_cert_host,
|
|
'CA_TRUST_WRAPPER_HOST': _playwright_ca_wrapper_host,
|
|
'CA_TRUST_NAME': _playwright_ca_trust_name
|
|
} if (_playwright_ca_required | bool) else {}
|
|
}}
|
|
register: _playwright_run
|
|
changed_when: false
|
|
failed_when: false
|
|
|
|
- name: 💬 Print Playwright output (on failure)
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
rc: {{ _playwright_run.rc | default('<absent: run was aborted>') }}
|
|
|
|
stdout:
|
|
{{ _playwright_run.stdout | default('') }}
|
|
|
|
stderr:
|
|
{{ _playwright_run.stderr | default(_playwright_run.msg | default('')) }}
|
|
when: (_playwright_run.rc | default(1)) != 0
|
|
changed_when: false
|
|
|
|
- name: 📝 Record failure
|
|
ansible.builtin.set_fact:
|
|
_playwright_failures: "{{ (_playwright_failures | default([])) + [application_id] }}"
|
|
when: (_playwright_run.rc | default(1)) != 0
|
|
|
|
- name: 🗄️ Archive Playwright reports for this variant + pass
|
|
ansible.builtin.script: >-
|
|
{{ role_path }}/files/archive_playwright_reports.sh
|
|
{{ _playwright_reports_dir | quote }}
|
|
{{ VARIANT_INDEX | int }}
|
|
{{ ASYNC_ENABLED | bool | string | lower }}
|
|
changed_when: true
|