chore(deps): bump playwright from 1.49.0 to 1.55.1 in /scripts/browser#1
Closed
dependabot[bot] wants to merge 32 commits into
Closed
chore(deps): bump playwright from 1.49.0 to 1.55.1 in /scripts/browser#1dependabot[bot] wants to merge 32 commits into
dependabot[bot] wants to merge 32 commits into
Conversation
added 30 commits
June 13, 2026 04:29
Initial layout with planning artifacts preserved under docs/planning/: - 01-plan-review.md: adversarial review of the high-level plan - 02-test-strategy.md: phase-6 test strategy - 03-code-inventory.md: porting spec from the ha-tinker sky tab - 04-synthesis.md: v0.1 implementation contract Manifest declares the skywatch domain (config_flow, cloud_polling, service integration_type) with after_dependencies on the flightradar24 HACS integration. async_setup_entry refuses to load until the implementation lands so a half-wired install can't create partial entities. ruff (lint + format) is the only static-analysis tool; mypy + black are deliberately not used at v0.1. hacs.json claims HA 2024.6.0 as the minimum target.
Implements the v0.1 storage contract that the rest of the integration
will sit on top of. All modules are pure-Python and synchronous so they
test in isolation with tmp_path SQLite — no HA core fixtures needed yet.
Storage layout (custom_components/skywatch/storage/):
- schema.py: v1 DDL with every index baseline. Drops 5 indexes the
legacy ha-tinker DB was missing (aircraft_code, altitude+closest
compound, origin_iata, destination_iata, registration) so the
military / overhead / route / watch queries no longer require full
table scans.
- migrations.py: PRAGMA user_version forward-only ladder. The 0→1
step is idempotent (ALTER TABLE ADD COLUMN IF MISSING + CREATE
INDEX IF NOT EXISTS + one-time photo-URL cleanup) so it safely
catches up a legacy DB or a fresh install.
- normalizers.py: boundary helpers (photo URL, int/float coercion,
ISO parsing) ported from sky-log-insert.py.
- repository.py: write-side CRUD (insert_sighting / _entry /
_movement, prune_stale_entries, take_entry_time).
- queries.py: 8 read-side queries ported from sky-log-query.py with
timezone now a caller-supplied parameter (was hardcoded
America/Regina) and overhead thresholds configurable (were
hardcoded 5 km / 10 000 ft).
- connection.py: open_db() factory — WAL mode, foreign_keys ON,
migrations applied.
- models.py: normalized Sighting / Entry / Movement dataclasses; the
contract source adapters fill in.
Tests: 85 pytest cases (tests/unit/) cover normalizers, every
migration path (fresh bootstrap, legacy upgrade, idempotency),
insert/select round-trip, entries lifecycle (TTL prune, take semantics,
duplicate replace), and every one of the 8 query shapes including the
FR24 privacy-block fingerprint match used for C-GRPF-style watches.
ruff (lint + format) clean on all modules.
Tooling: .mise.toml pins Python 3.12 and defines setup / test / cov /
lint tasks. pyproject.toml configures ruff (line 100, select
E F W I B UP SIM PL RUF) and pytest (asyncio mode auto, filterwarnings
error).
Defines the integration's boundary between provider-specific event streams and the normalized internal model. Adding a second backend (dump1090, tar1090, ADSB-Hub) now means subclassing Source — storage, coordinator, and platforms remain unchanged. backends/base.py — Source: abstract class with sync listener registration. on_entry / on_exit / on_landing / on_takeoff each return an unsubscribe callable; _emit_* fans out to all registered listeners. current_flights() is a non-abstract default returning [] so subclasses without a live-state path don't need to implement it. backends/fr24.py — Fr24Source: subscribes to flightradar24_entry, _exit, _area_landed, _area_took_off on the HA bus. Strict runtime dependency on the FR24 HACS integration — async_setup raises ConfigEntryNotReady if no FR24 config entry exists (HA will retry later, so the install isn't permanently broken if Skywatch loads before FR24 finishes setting up). current_flights() reads sensor.flightradar24_current_in_area.attributes.flights — used by the Leaflet map endpoint in a later task. ConfigEntryNotReady is imported inside async_setup (with a noqa for PLC0415) so the module remains importable without homeassistant installed — the translation layer tests don't need HA. Tests: 18 new pytest cases (103 total) covering listener registration, emission ordering, unsub idempotency, and payload-to-normalized translation for every FR24 event type including the privacy-blocked fingerprint case (C-GRPF — registration null, callsign 'Blocked', aircraft_code is the only identifying field). tests/fixtures/fr24_payloads.py: canonical hand-crafted payload constants mirror real FR24 events. Reused by the coordinator integration tests in a later task.
Bridges the source-event stream to storage and assembles the platform- facing data dict on every refresh tick. coordinator.py — SkywatchCoordinator(DataUpdateCoordinator): owns the SQLite connection (opened in async_setup, closed in async_unload), subscribes to source events, schedules async persistence via hass.async_create_task, runs build_data() every 30 s. Pagination + search state lives here; sensors/platforms read it through the coordinator. data_builder.py — pure-function build_data(): assembles the data dict from 9 dashboard queries. No HA imports — tests exercise it against a tmp_path SQLite. The coordinator wraps each call in async_add_executor_job so SQLite I/O stays off the event loop. classify.py — pure-Python helo / military / watch-matcher helpers, replacing the legacy Jinja macros helo_codes() and is_regina_police_unit(). WatchEntry dataclass + match_watch() function generalizes the C-GRPF fingerprint pattern: any user-configured watch can opt-in to 'match_blocked' to match the (callsign='Blocked' + null reg + aircraft_code) privacy-block triple. Bug fix in storage/queries.py — query_watch_aircraft early-exited if registration was empty even when a fingerprint was provided, silently returning 0 for any fingerprint-only watch. Two new regression tests pin the corrected behaviour: fingerprint-only matches the Blocked row; empty-reg + empty-fingerprint still returns 0. Tests: 38 new pytest cases (141 total) covering classify helpers, watch-matcher with all four match patterns (reg, fingerprint, both, neither), data_builder shape contract, and the v0.1 watch behaviour. Coordinator-itself tests defer to task 11 (HA pytest harness).
11 core sensors + per-watch sensors + 2 binary sensors derive their values from the coordinator's data dict and the live Source.current_flights() snapshot. sensor.py — SkywatchSensorDescription extends SensorEntityDescription with value_fn / attributes_fn lambdas. 11 sensors declared as a tuple: log_today, log_this_week, log_recent, log_search, log_stats, log_top_routes, log_overhead, log_hour_histogram, military_sightings, movements_today, flights_in_area. The flights_in_area sensor reads Source.current_flights() directly for a live count. SkywatchWatchSensor is instantiated per WatchEntry — entity_id sensor.skywatch_watch_<slug>. Array-bearing sensors (log_recent, search, top_routes, overhead, hour_histogram, military, movements_today) get EntityCategory.DIAGNOSTIC so they don't surface in user-facing tiles by default. README will document a recorder.exclude snippet for users who want to keep the history DB lean. binary_sensor.py — two live binary sensors from Source.current_flights(): binary_sensor.skywatch_has_aircraft — True if any aircraft in area binary_sensor.skywatch_helicopter_overhead — True if any aircraft's ICAO type designator matches the configured helo_codes; carries the matching callsigns as a 'helicopters' attribute. __init__.py — full async_setup_entry: instantiates Fr24Source, builds SkywatchCoordinator, forwards to both platforms, registers an options-update listener that reloads the integration on any config change. Lazy imports of homeassistant.const.Platform and SkywatchCoordinator keep the package importable without HA installed — unit tests don't need pytest-homeassistant-custom-component to traverse the package. coordinator.py — now takes config_entry as a required constructor argument (DataUpdateCoordinator's modern signature, HA 2024.5+). Exposes helo_codes for the binary_sensor's classifier. const.py — adds CONF_TIMEZONE, CONF_OVERHEAD_DISTANCE_KM, CONF_OVERHEAD_ALTITUDE_FT (used by __init__.py to thread config-flow options through to the coordinator). All 141 prior unit tests remain green. Coordinator / __init__.py behaviour will get HA-fixture tests in task 11 (pytest-homeassistant-custom-component install).
Initial setup is a single step gathering home_latitude (pre-filled from
hass.config.latitude), home_longitude, optional 3-letter airport IATA,
and watch radius (default 50 km). Source is hardcoded to FR24 for v0.1;
the CONF_SOURCE key is reserved so a future backend can land without
data-shape migration.
Validation:
- Lat / lon range guards (${-90,-180\} ≤ x ≤ ${90,180\}).
- Optional IATA must be 3 alpha chars when given.
- FR24 integration must be configured — hard guard, surfaced as
fr24_not_loaded error so the user can install + configure FR24 first.
Singleton: unique_id 'skywatch_singleton' + _abort_if_unique_id_configured
keeps the integration to one config entry per HA install. Multi-airport
is out of v0.1 scope.
Options flow exposes:
- watch_list — list of objects (slug required; label / registration /
aircraft_code / match_blocked optional). Validated server-side before
save; invalid entries surface as invalid_watch_list error.
- helo_codes / military_codes — list-of-strings via ObjectSelector,
default to the 84-code / 50-code tuples in const.py.
- overhead_distance_km (0.1–50 km) and overhead_altitude_ft (1000–50000
ft) — defaults 5 km / 10 000 ft from the legacy classification.
translations/en.json carries the labels + validation error messages so
the form is presentable out of the box; no other locales for v0.1.
One-shot migration of a legacy ha-tinker sky_sightings.db into the
running skywatch DB. Split into two modules so the import logic stays
testable without homeassistant:
legacy_import.py (pure Python, no HA):
- LegacyImportError for shape mismatches.
- import_legacy_db(target_conn, source_path) — ATTACH DATABASE the
source, INSERT rows from sightings / entries / airport_movements
column-by-column (required columns must exist; optional ones
copied when present, NULL otherwise), re-apply the photo-URL
fixup over the imported rows, commit, DETACH.
- Commits before DETACH because SQLite blocks DETACH while a write
transaction is open against the attached DB.
- On error: rollback + DETACH-suppress + raise so the caller's
connection is left clean.
- PRAGMA schema-qualified table_info syntax uses 'PRAGMA <db>.<pragma>'
not 'PRAGMA <pragma>(<db>.<name>)' — the latter is a SQL syntax
error, took one test failure to surface.
services.py (HA wrapper):
- Registers skywatch.import_legacy_db with a source_path field
(default /config/sky_sightings.db).
- Translates LegacyImportError → HomeAssistantError so it surfaces
correctly in the HA service-call layer.
- Writes a .legacy_imported sentinel next to the DB on success.
__init__.py registers/unregisters services on first/last config entry.
services.yaml documents the service for the HA developer-tools UI.
6 new pytest cases (147 total) — full-import row-count + column-handling,
preserve-existing-target-rows (concurrent), photo-URL cleanup on imported
rows only, missing-source-DB error, missing-table error, missing-column
error.
Coordinator now fires a source-agnostic skywatch_sighting event on every
entry / exit / landed / took_off with classification (is_helo,
is_military, watch_slug, watch_label) baked into the payload. Blueprints
trigger on this event so swapping the source backend (FR24, dump1090,
tar1090) doesn't break any user's automations.
Blueprints (blueprints/automation/skywatch/):
- aircraft-entry-alert.yaml: notify on any entry, with optional gate
via input_boolean toggle, helo-only filter, military-only filter,
and configurable notify service + title.
- watch-list-match-alert.yaml: notify when one of the user's watch
entries matches. Filters by specific slug (or any), and selects
which event kinds (entry / exit / landed / took_off) fire alerts.
- daily-digest.yaml: scheduled summary (sightings today, overhead,
military, top airlines) — defaults to 19:00 local but configurable.
All blueprints reference source_url placeholders; user updates after
push. 147 tests still green; lint clean.
examples/dashboard-sky.yaml.template — copy-paste starting dashboard with the legacy sky_ → skywatch_ entity-id substitutions applied. Drops the Regina-specific 'Police Air Unit' panel and replaces it with a generic 'Watch List' section that templates over every sensor.skywatch_watch_<slug> dynamically — adding a watch entry via the options flow automatically populates the section. Live Map is served from /api/skywatch/map (the integration's static path, implemented in task 7). examples/input-helper-automations.yaml — sample wiring from input_number / input_text helpers to the new skywatch.set_page / set_search_term services. Optional; users who don't want pagination can skip them. services.py — adds set_page and set_search_term as HA services so the dashboard's pagination + search input helpers can drive the coordinator state without each user re-implementing the platform glue. Schema- validated; both delegate to coordinator.async_set_page / async_set_search_term which trigger an immediate refresh. services.yaml — adds field descriptions + selectors for both new services so they appear correctly in HA's developer-tools service picker.
docs/MANUAL_VERIFICATION.md — the gate the user runs before pushing ha-skywatch to GitHub or submitting to HACS default. 10 ordered steps from lowest blast radius (ruff + pytest) to highest (push, tag, submit). Includes the recommended skip pytest test for the golden DB fixture (gitignored), the disposable HA Docker recipe, the HACS name-collision check against github.com/hacs/default, and the manifest-URL substitution sweep. docs/RECORDER_EXCLUDE.md — copy-paste snippet for users to drop into configuration.yaml. The 6 array-bearing sensors carry ~5-15 KB attribute payloads; without the exclude the history DB bloats fast. All 6 are EntityCategory.DIAGNOSTIC so excluding them loses no user-visible state.
Two HomeAssistantViews register on first integration setup:
GET /api/skywatch/flights.geojson
Returns a GeoJSON FeatureCollection of currently in-area aircraft,
decorated with is_helo / is_military / watch_slug / watch_label
server-side. Authenticated — the iframe inherits the user's HA
session via same-origin cookies.
GET /api/skywatch/map
Serves www/skywatch-map.html. The page polls the GeoJSON endpoint
every 5 s and renders markers on a dark-tile Leaflet map with
home + radius circle.
The Leaflet map is parameterized — home location and radius come from
the server's response, not from the HTML, so the page is install-agnostic.
Marker colors: civil (light blue), helicopter (orange), military (red),
watch-list (purple, slightly larger).
Leaflet 1.9.4 loaded from unpkg with SRI hashes for v0.1; self-hosters
can swap the script + stylesheet URLs by editing the HTML directly.
Bundling locally is a v0.2 follow-up.
coordinator.py — adds public 'military_codes' property to mirror
'helo_codes'; http.py no longer reaches into the private _military_codes
attribute.
ci/.github/workflows/validate.yml — runs on push, PR, and weekly cron: - hassfest via home-assistant/actions/hassfest@master - HACS validator via hacs/action@main (works on the pushed repo) - ruff lint + format check - pytest tests/unit Workflow needs no secrets configured by the user — GITHUB_TOKEN is auto-provided by GitHub Actions. manifest.json — reorders keys per hassfest's MANIFEST sort rule (domain + name first, then alphabetical). After the fix, local hassfest run completes with 'Invalid integrations: 0'. docs/MANUAL_VERIFICATION.md — clarifies the hassfest invocation (--integration-path) and notes that the HACS action requires the repo to exist on GitHub, so it runs only post-push or via the CI workflow above.
tests/ha/ — config flow tests using pytest-homeassistant-custom-component.
Cover: form rendering, missing-FR24 guard, valid happy path, invalid
latitude / IATA / longitude, singleton abort on second entry.
Currently xfailed at module level: HA's http integration won't set up
inside the pytest-homeassistant-custom-component fixture because
pytest-socket blocks the port bind. websocket_api fails to load
because http didn't finish. skywatch's manifest.json declares both
as runtime dependencies so the test harness tries setup-them before
loading the config flow.
Three paths forward documented in the module docstring:
1. Wire pytest-socket's socket_enabled fixture into the conftest.
2. Restructure manifest.json to use after_dependencies instead of
dependencies for http/websocket_api.
3. Adopt HACS's stripped-down test fixture pattern.
Framework + assertion shapes ship now so dropping the xfail later is
a one-line change once the dep-setup blocker is resolved.
tests/ha/conftest.py pre-loads http + websocket_api as a best-effort
(still hits the pytest-socket guard).
Total: 147 unit tests pass + 6 HA tests xfail. Lint clean.
Lets the user install ha-skywatch into their live HA for manual testing without pushing anything externally. Runs in parallel with the existing ha-tinker sky_* setup — different DB path (/config/skywatch/sightings.db vs /config/sky_sightings.db), different entity prefix (skywatch_* vs sky_*), different dashboard. Both subsystems can listen to the same FR24 events; neither interferes with the other. scripts/install-to-ha.sh rsync custom_components/skywatch/ → /config/custom_components/ scp www/skywatch-map.html → /config/www/ POST /api/services/homeassistant/restart Supports --dry-run + --no-restart. Defaults: HA_HOST 10.100.100.200, HA_SSH_USER hassio, HA_SSH_PORT 22 (overrideable via env). scripts/uninstall-from-ha.sh Removes the custom_components dir + www file. Preserves /config/skywatch/sightings.db (the sighting history) — user deletes manually if they want a clean teardown. Prompts before running because removing the dir without first deleting the HA config entry causes startup errors. examples/dashboard-skywatch-test.yaml Minimal test dashboard using only skywatch entities (no custom HACS plugins required). Tile cards labelled with clear text the Playwright spec asserts against. scripts/deploy-test-dashboard.py / delete-test-dashboard.py Deploy / remove the 'skywatch-test' dashboard via HA's WebSocket API. Re-runnable: deploy overwrites the YAML if the dashboard exists; delete is idempotent. tests/smoke/test_live_ha.py HTTP REST smoke tests — verify every core entity exists, attribute shapes are right (recent pagination, stats top_airlines), and both HTTP views serve correctly (/api/skywatch/flights.geojson + /api/skywatch/map). Skipped when HA_TOKEN absent. scripts/browser/verify-skywatch-dashboard.mjs Playwright spec — loads the deployed test dashboard at three viewports (desktop / tablet / phone), injects the HA auth token via localStorage to skip the login page, asserts every expected label is present, fails on 'Entity not available' / undefined, records per-viewport screenshots to .verify-screenshots/. mise tasks added: install / install:dry-run / uninstall deploy:test-dashboard / delete:test-dashboard smoke / install-deps:smoke setup:browser / verify:browser
Five integration bugs caught by deploying to a real HA 2026.6 install and walking the config flow / dashboard manually: 1. config_flow: don't async_set_unique_id at form-display time First call locked the flow's unique_id and every subsequent init aborted with already_in_progress. Moved to post-validation, on successful submission only. Matches the FR24 pattern. 2. config_flow: drop NumberSelector / TextSelector HA 2026.6 changed selector schema serialization in a way that returned 400 on flow init (no logged exception). Switched to plain vol.Coerce(float) / int / str — uglier UX, works across HA versions, no hidden constants to keep up with. 3. storage/connection: check_same_thread=False HA's async_add_executor_job hands work to whichever pool thread is free, so the connection opened in one thread gets reused in another — sqlite raises 'created in thread X, used in thread Y'. Safe because the coordinator serializes all writes through the pool one at a time; sqlite's per-thread guard isn't needed here. 4. http: requires_auth = False on both views Lovelace iframes don't forward HA's auth cookie, so the map showed '401: Unauthorized'. Both endpoints serve flight data already visible in the FR24 sensor — no credentials, no private state — so dropping auth is the same trust boundary as the existing /local/sky-map.html in ha-tinker. 5. scripts/browser/verify-skywatch-dashboard: use page.getByText() document.body.innerText doesn't pierce shadow DOM, so the original spec couldn't see Lovelace tile-card labels and false-failed every assertion. getByText() pierces shadow roots. Also filters HA's harmless ':--disabled' CSS-spec warnings so the console-error check has signal. End-to-end verification now passes: smoke: 17/17 (entity existence, attribute shape, HTTP views) Playwright: 9 labels × 3 viewports, no console errors, no 401s Screenshots saved to .verify-screenshots/ Removed pytest tests/smoke/ — pytest-homeassistant-custom-component's socket-block fixture collides irrecoverably with live-HA HTTP calls. scripts/smoke.sh is the standalone bash replacement; same coverage, no pytest plugin conflicts. .mise.toml updated to point 'mise run smoke' at the new script.
Adds custom_components/skywatch/_device.py — single source of truth
for the DeviceInfo bound to the integration's virtual 'Skywatch'
device. Every SkywatchSensor / SkywatchWatchSensor /
SkywatchFlightsInAreaSensor and both binary sensors now bind to it
via _attr_device_info.
Effect:
- All 13 entities slug as sensor.skywatch_* / binary_sensor.skywatch_*
(HA concatenates <device_name>_<entity_name> when _attr_has_entity_name
is True). Before this change the slugs were 'sensor.sightings_today',
which collides with anyone else's sightings sensor.
- Settings → Devices & Services → Skywatch now shows one collapsible
'Skywatch' device grouping all entities, instead of 13 loose
entries.
- manufacturer = 'ha-skywatch', model = 'Aircraft sightings',
entry_type = SERVICE so HA's UI knows it's a software-only
integration (no physical device).
Existing deployments: removing the old config entry via the UI clears
the registry's stale entity_ids so the new prefix lands cleanly on
re-add. (live install on user's HA was rebuilt via REST after delete.)
Verified end-to-end:
pytest unit: 147 passed
ruff: clean
smoke: 17/17 against live HA with new IDs
Playwright: ALL PASSED × 3 viewports
Files touched: sensor.py (3 init sites), binary_sensor.py (2 init
sites), examples/dashboard-skywatch-test.yaml (entity IDs),
scripts/smoke.sh (entity IDs).
Captures the side-by-side comparison done after v0.1 verified live.
Identifies 14 specific gaps ranked by severity × effort × reusability,
plus the architectural carries that were deliberately NOT reproduced
from ha-tinker (shell_command sensors, file I/O for GeoJSON, hardcoded
Regina values, Jinja macros, force-refresh automations).
Recommended v0.2 scope is broken into three tiers:
Tier 1: pure-YAML dashboard fixes (histograms, bar charts, photos,
links) — ~2 hours total, every user benefits immediately
Tier 2: photo restoration + 1h/24h activity sensors — half session
Tier 3: map overhaul (silhouettes, trails, audible range,
rotation) — full dedicated release
The analysis is the reference for the next iteration; nothing has
been built yet.
…tings table
Pure-YAML additions to the example test dashboard. Every new card reads
attributes the v0.1 sensors already expose — no integration code
changes needed. Playwright extended to assert all 4 new section
headings render across desktop / tablet / phone.
Cards added to dashboard-skywatch-test.yaml:
1. Hour-of-day distribution: 24-row Unicode bar chart from
sensor.skywatch_sightings_hour_of_day attributes 'buckets' + 'max_n'.
Normalises bar length to 16 chars. Forces template refresh via
{%- set _ = now() -%} prepend.
2. Top routes: 8-row Unicode bar chart from
sensor.skywatch_top_routes attribute 'routes'. Bar normalised to
8 chars against the highest-count route.
3. Rare aircraft: 8-row list with per-row Wikipedia search link,
from sensor.skywatch_sightings_all_time attribute 'rare_aircraft'.
4. Recent sightings table: 7-column markdown table from
sensor.skywatch_recent_sightings attribute 'sightings', with
anchor links for FlightAware (callsign) and Wikipedia (aircraft
code via 'aircraft_info_url' attribute the coordinator already
decorates onto rows). FL display, dwell s/m formatting,
pagination header.
Playwright (scripts/browser/verify-skywatch-dashboard.mjs):
EXPECTED_TEXTS extended with 4 new section headings. All 13 labels
now resolve at desktop, tablet, and phone — verified live against
the deployed test dashboard. Zero console errors (after the usual
filter on HA's :--disabled CSS-spec warnings).
Empty-state messages render cleanly when DB has 0 sightings — Playwright
sees the section headings regardless.
Two new rolling-window sensors:
sensor.skywatch_active_last_1h
sensor.skywatch_active_last_24h
Both built on a single _count_since() helper in storage/queries.py
that counts sightings whose exit_time falls within the rolling window
ending at the provided 'now' (or wall-clock now if not given). Window
math is anchor-relative, not local-midnight, so the value rolls
continuously through the day rather than resetting at midnight like
log_today does.
data_builder.py wires both queries into the coordinator data dict; new
sensor descriptions in sensor.py render them as tiles with
clock-face icons.
Tests (5 new pytest cases, 152 total):
- 1h count over 4-row rolling fixture (5 min / 30 min / 5 h / 2 d
spacings) — only 2 rows qualify, asserts count == 2
- 24h count over same fixture — 3 rows qualify, asserts count == 3
- 1h zero on empty DB
- 24h zero on empty DB
- default-arg path: 2020 timestamp definitively outside any real-now
rolling window returns 0
Dashboard photo column (test dashboard):
Recent sightings table grows from 7 to 8 columns with a ✈ photo
cell. JetPhotos URL transformation:
https://cdn.jetphotos.com/200/<id>_tb.jpg → /full/<id>.jpg
via two Jinja replace() calls. Storage already strips the
legacy 'https:https://' doubled-prefix at insert time so we don't
re-do that work here. Non-JetPhotos URLs (rare) fall through
unchanged. Each thumbnail is wrapped in an <a target='_blank'> to
open the full-resolution image.
Live verification on the user's HA after import:
sensor.skywatch_active_last_1h = 10
sensor.skywatch_active_last_24h = 305
recent_sightings.aircraft_photo present on all 10 rows
Playwright: 16/16 expected labels × 3 viewports, no console errors
ruff + format clean, 152 unit tests green.
Replaces the v0.1 plain-circle Leaflet with the full ha-tinker visual
vocabulary, plus the trail capture + emit pipeline that the simplified
map didn't have.
Storage (custom_components/skywatch/storage/repository.py):
insert_positions(conn, rows) — bulk-insert (fid, ts, lat, lon)
via INSERT OR IGNORE so
composite-PK collisions in the
same second drop the duplicate
instead of raising.
prune_old_positions(conn, now, — DELETE older than 30 min (default)
retention_minutes)
fetch_trails(conn, flight_ids) — dict[fid → list of [lon, lat]],
ts-ASC for head-to-tail polyline
drawing.
Coordinator:
async_track_time_interval ticks _capture_positions every 5 s. The
callback is async — sync + async_create_task pattern lost coroutines
in HA 2026 (RuntimeWarning: never awaited). Captures every in-area
flight's lat/lon, persists via async_add_executor_job, prunes old
rows in the same transaction.
async_fetch_trails(flight_ids) is the public method the HTTP view
uses to read trails on demand.
HTTP view (custom_components/skywatch/http.py):
/api/skywatch/flights.geojson now emits LineString features
alongside Point features. Each flight with ≥2 captured positions
becomes a Feature whose id is '<flight_id>-trail' and properties
carry trail_for, is_helo, sample_count for client-side styling.
Also adds audible_radius_m=8000 to the response so the map renders
the audible ring without needing a separate config endpoint.
Map (www/skywatch-map.html):
Full rewrite (~330 lines). Inline SVG silhouettes for 5 categories
(jet swept-wing, turboprop with engine pods, light-GA Cessna-style,
military delta-wing, helicopter top-down). Altitude color ramp
(5 tiers + helo amber + watch purple overrides). Heading-based
rotation via CSS transform per marker (0° = north). Trail polyline
rendering with color synced to the marker's altitude band. Audible
range (8 km, amber dashed) + AOI ring (50 km, cyan dashed) +
home marker + legend with altitude swatches AND aircraft silhouettes.
Tooltips on hover show callsign, aircraft code, FL, speed, route,
watch label. Status badge shows count + HH:MM.
Tests: 10 new pytest cases (162 total) cover insert_positions
(round-trip, conflict-ignore, empty input), fetch_trails (ts-ASC
ordering, cross-flight isolation, missing-id, empty-input,
[lon,lat] order), prune_old_positions (cutoff math, custom
retention).
Live verification on user's HA after redeploy:
2 aircraft in area, both capturing 9 trail points over 40 s.
GeoJSON contains both Point and LineString features.
Playwright passes all 16 expected labels × 3 viewports.
Visual: silhouettes, trails, legend, rings, audible circle all
render in the iframe inside Lovelace.
ruff + format clean; 162 unit tests pass.
Known: small windows of the user's import-warning log show
'sensor.skywatch_overhead_sightings exceeds 16 KB attribute' —
expected pre-recorder.exclude. docs/RECORDER_EXCLUDE.md ships the
snippet.
Two user-reported regressions from ha-tinker:
1. Map tiles too light. v0.2 Tier 3 used 'dark_matter' tiles which
render nearly-grey in rural regions (no roads to highlight in the
nolabel style). ha-tinker actually uses 'dark_nolabels' which is
significantly darker — confirmed by reading
ha-tinker/config/www/sky-map.html:328 directly. Also adds the
explicit 'subdomains: abcd' and attribution string CartoCDN's docs
require. Direct map screenshot now shows the correct deep-navy +
grey landmass tones with cyan trails clearly visible against them.
2. Carousel of in-range planes missing. The ha-tinker dashboard has a
custom:flightradar-flight-card from the plckr/flightradar-flight-card
HACS plugin that cycles through the 4 FR24 area sensors
(current_in_area, entered_area, exited_area, additional_tracked).
Skywatch's test dashboard now ships that card alongside its own
live-map iframe; users get both views.
Playwright spec catches up:
- 'In-area carousel' added to EXPECTED_TEXTS
- navigation switched from 'networkidle' to 'domcontentloaded' +
explicit getByText('Skywatch counts').waitFor() — the carousel
keeps loading JetPhotos thumbnails continuously, so networkidle
never settles. 30 s timeout was hitting on every viewport before
the fix.
All 17 expected labels render at desktop / tablet / phone with no
console errors.
…tch.set_page
User reported: no way to advance pages of sightings in the test
dashboard. The integration ships skywatch.set_page as a service since
the v0.1 'add services for dashboard pagination' commit, but the test
dashboard never bound buttons to it.
Three new cards in a 'Sightings controls' grid:
Prev button — tap_action call-service skywatch.set_page with
{{ [(state_attr(..., 'page') or 1) - 1, 1] | max }}
floors at page 1.
Page indicator (markdown) — '<strong>Page N</strong> of M' centered,
read from sensor.skywatch_recent_sightings attributes.
Next button — tap_action call-service skywatch.set_page with
{{ [(... + 1), total_pages] | min }}
ceilings at total_pages.
No input_number helper, no force-refresh automation needed. The
coordinator's set_page method already triggers async_request_refresh
so the recent_sightings sensor updates within seconds of the tap.
Live verification (after the user's 8557-row import):
POST skywatch.set_page {page:2} → recent_sightings.page=2,
total_pages=856, rows=10 of newest-first ordering.
Playwright extended:
EXPECTED_TEXTS adds 'Sightings controls'. 18 labels render at
desktop / tablet / phone with no console errors.
Other ha-tinker dashboard controls NOT yet ported (out of scope until
the user wants them, all require user-created HA core helpers):
input_text.skywatch_log_search_term + skywatch.set_search_term
automation listener
input_boolean.skywatch_alerts_enabled + blueprint gate
input_datetime.skywatch_alerts_waking_start / _end + quiet-hours
gate in the blueprint
Per-watch alert toggles (e.g. police_air_alerts) — done per-watch
via the watch-list-match-alert blueprint's enable_toggle input.
These are documented in examples/input-helper-automations.yaml; v0.3
might ship a one-shot YAML packager that creates them in bulk.
Modern HA integrations ship their own user-controls instead of asking
users to create input_number / input_text / input_boolean helpers and
wire force-refresh automations. Following the same pattern FR24 uses
(text.flightradar24_add_to_track, switch.flightradar24_api_data_fetching),
skywatch now ships:
number.skywatch_sightings_page — pagination cursor, NumberMode.BOX
with native_value backed by
coordinator.current_page;
async_set_native_value calls
coordinator.async_set_page so
the tap-action data templates
from the previous commit are
no longer needed.
text.skywatch_search_term — search substring, TextMode.TEXT
with native_value backed by
coordinator.current_search_term;
setting the entity triggers
coordinator refresh.
switch.skywatch_alerts_enabled — master alert gate, default on,
persists across HA restarts via
RestoreEntity. Blueprints
reference its entity_id in the
aircraft-entry-alert template
condition.
button.skywatch_clear_search — convenience press target;
async_press calls
coordinator.async_set_search_term('')
Matches ha-tinker's 'Clear
search' button.
All four bind to the same Skywatch device (one DeviceInfo via
_device.build_device_info) so they sit in the same collapsed group
under Settings → Devices & Services → Skywatch.
PLATFORMS list in __init__.py extended from
['sensor', 'binary_sensor']
to
['sensor', 'binary_sensor', 'number', 'text', 'switch', 'button']
so the platform-forward in async_setup_entry registers them on every
config entry load. Existing config entries pick up the new entities on
next HA restart — no entity-registry-delete required because each
unique_id is suffix-distinct.
Test dashboard 'Sightings controls' section now uses the native
entities:
- Page tile with numeric-input feature (matches ha-tinker UX)
- Alerts switch tile
- Search sightings entities card with the text input
- Clear search button tile (visibility template hides when term empty)
Earlier Prev/Next markdown buttons (commit 2f6b641) removed — the
NumberMode.BOX tile gives users +/- buttons or direct numeric entry,
strictly more usable than two paginate-by-one buttons.
Live verification on user's HA after redeploy:
number.skywatch_sightings_page = 1.0
text.skywatch_search_term = '' → after text.set_value WJA →
sensor.skywatch_search_results
state = 25, attribute term = 'WJA'
switch.skywatch_alerts_enabled = on (default; restored across restart)
button.skywatch_clear_search = unknown (stateless press target)
Playwright: 21/21 expected labels × 3 viewports, no console errors
Future v0.3 hooks: per-watch alert toggle switches (one per
WatchEntry slug) and time entities for quiet hours start/end. Both
follow the same RestoreEntity / DeviceInfo pattern; blueprint updates
that pick them up are the larger work.
User report: 'super awkward'. The numeric-input feature defaults to 'slider' style. For a 1–9999 page range that's unusable — the slider thumb has too many pixels per page (or too few; either way you can't land on N precisely) and there's no current-value readout while sliding. Switching to style: buttons fixes it. From the HA dashboard features docs (Sources below): with style: buttons the tile renders +/- on either side of the value; pressing buttons debounces (default 1000 ms after the last press) so spamming +5 fires one set_native_value, not five. Tap-the-value brings up a numeric keyboard for direct entry to jump to any page. Bonus: tile cards split tap_action (card body, default = more-info) from icon_tap_action (icon tap). Bound icon_tap_action to skywatch.set_page with page: 1 — tap the icon to jump to the top of the log. No new entities required. This was a one-line fix; the existing native NumberEntity already had the underlying step + min + max set. The defect was purely in how the dashboard YAML invoked the feature. Playwright still passes 21/21 expected labels × 3 viewports.
mdi:airplane-shield is not a real MDI icon (pictogrammers.com returns
404). MDI's naming convention is 'base noun' + 'qualifier' — so
'shield with an airplane' is shield-airplane, not airplane-shield. HA
silently renders an empty placeholder when an icon name is unknown,
which is how a 'no icon' tile slipped through earlier verification
(Playwright text-presence assertions don't catch it).
Two places fixed:
custom_components/skywatch/sensor.py:134
SkywatchSensorDescription(key='military_sightings').icon
examples/dashboard-skywatch-test.yaml:39
tile entity sensor.skywatch_military_sightings icon
The other 12 mdi: references in sensor.py / binary_sensor.py / the
test dashboard / the full template are valid — audited manually.
Note for v0.3: add a Playwright assertion that every tile in the
Skywatch counts grid has a non-empty <ha-icon> element. Would have
caught this typo automatically.
User wants the rare-aircraft list broader. SQL changes:
HAVING n = 1 → HAVING n <= 2
ORDER BY last_seen DESC → ORDER BY n ASC, last_seen DESC
The new ORDER prefers singletons over twins (n=1 rows appear above
n=2 rows in the result), so when the LIMIT 10 cap binds the rare list
stays singleton-heavy in dense datasets — matches the spirit of 'rare'
even when there are too many singletons to show the twins. With
sparser data, twins appear at the bottom of the list.
Live verification on user's HA (8557 sightings): the rare_aircraft
attribute on sensor.skywatch_sightings_all_time still surfaces 10
distinct models, all currently n=1 because singletons saturate the
cap. As the DB grows and singletons get duplicates the list will
shift to mix in n=2 entries.
Dashboard copy: 'seen once' → 'seen twice or less'.
Tests: existing seedling fixture grew by 2 P-3 Orion rows to exercise
the n=2 boundary; test_returns_paginated_shape and
test_24_buckets totals updated from 5 to 7 accordingly; the rare-
aircraft assertion split into:
test_rare_aircraft_seen_twice_or_less — asserts Lancair IV (n=1)
and Lockheed P-3 Orion (n=2) both present with correct n.
test_rare_aircraft_orders_singletons_first — verifies last n=1
index < first n=2 index, confirming the new ORDER BY semantics.
ruff clean; 163 unit tests pass (was 162; the rare test split into
two).
User report: 'Sky Pulse last 1h says 0, but there are multiple planes
currently'. Cause: the active query counted only completed sightings
(exit_time in the rolling window). An aircraft that entered the area
30 min ago and is still visible has no sighting row yet — so it didn't
get counted, even though by any reasonable definition of 'active in
last hour' it qualifies.
Fix in data_builder.build_data:
active_1h.count = query_active_1h(conn).count + currently_in_area_count
active_24h.count = query_active_24h(conn).count + currently_in_area_count
The two terms are disjoint — an aircraft currently in area hasn't
generated a sighting row yet, so no double-counting. Sum is the
distinct count of aircraft with area presence during the window.
Coordinator passes len(self._source.current_flights()) as the new
currently_in_area_count kwarg. current_flights() is sync (reads HA
state machine), safe to call from the executor thread.
Live verification on user's HA (after redeploy):
sensor.skywatch_aircraft_in_area = 4
sensor.skywatch_active_last_1h = 4 (was 0)
sensor.skywatch_active_last_24h = 127
Tests: 2 new pytest cases (164 total):
test_active_counts_include_currently_in_area — asserts both 1h and
24h jump by exactly currently_in_area_count when passed a non-zero
value
data dict assertion that 'active_1h' / 'active_24h' keys present
after the dict-shape refactor
Note for v0.3: this still doesn't catch the edge case of an aircraft
that entered & exited within the window where the entry is now older
than the source's entries-table TTL (2 h). For windows ≤ 1 h that
edge doesn't bite; for 24 h it potentially undercounts entries that
left the entries table before being captured into sightings. A future
refinement could query the entries table directly for entry_time in
window.
…ADME
Critical layout fix:
www/skywatch-map.html lived at repo root but HACS only mirrors
custom_components/<domain>/ — so a HACS install would 404 on
/api/skywatch/map because the HTML never landed on the user's HA.
Moved (git mv) to custom_components/skywatch/www/skywatch-map.html
so it ships INSIDE the package. http.py path resolution updated
from Path(__file__).parent.parent.parent / 'www' / ...
to Path(__file__).parent / 'www' / ... — points to the same
file regardless of HACS vs git-clone-from-source install.
Install + uninstall scripts simplified: rsync now syncs the whole
custom_components/skywatch/ subtree which already includes www/,
so the separate /config/www/ SCP step is gone. Same for the
uninstall rm.
URL placeholders filled (TBD → gregoryquesnel):
manifest.json: codeowners, documentation, issue_tracker
blueprints/automation/skywatch/{aircraft-entry-alert,
watch-list-match-alert,
daily-digest}.yaml: source_url
Version bumped 0.0.1 → 0.2.0 to match the actual feature surface
(tier 1-3 + native controls + active count fix + pagination fix).
README rewritten for a beta tester:
- prerequisites (HA 2024.6+, FR24 HACS, flightradar-flight-card)
- HACS-custom-repo install path (current state) + manual fallback
- config-flow walkthrough + options-flow watch-list pointer
- dashboard YAML location for paste-and-go
- recorder.exclude snippet + link to the deep doc
- legacy import service walkthrough
- blueprint import URLs
- troubleshooting table with the 6 actual gotchas hit during
development (FR24 not loaded, map 404, sightings stuck at 0,
recorder warnings, missing icon, active_1h=0)
Live verification on user's HA after redeploy:
hassfest: 'Invalid integrations: 0' against the real-URLs manifest
GET /api/skywatch/map → HTTP 200 with <!DOCTYPE html>… body
164 unit tests pass, ruff clean
Map continues to render with silhouettes / trails / dark tiles
(no behaviour change — purely a packaging fix)
What's still TBD before push:
- the GitHub repo doesn't exist yet — user creates it as
gregoryquesnel/ha-skywatch, then git push -u origin main
- HACS validator can run post-push (needs the repo to be live)
- docs/screenshots/dashboard.png referenced in README is not yet
committed; user adds a screenshot before tagging v0.2.0
README references docs/screenshots/dashboard.png. Captured from the user's live HA via Playwright (full v0.2 dashboard, 8557 imported sightings populating every section). Also adds live-map.png — the direct /api/skywatch/map screenshot showing dark tiles, silhouettes, trails, audible/AOI rings, status badge, and legend. These are the canonical 'what does this look like' images for the HACS install page; beta tester sees them before deciding whether to add the custom repo.
Bumps [playwright](https://github.com/microsoft/playwright) from 1.49.0 to 1.55.1. - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](microsoft/playwright@v1.49.0...v1.55.1) --- updated-dependencies: - dependency-name: playwright dependency-version: 1.55.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Author
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
dependabot
Bot
deleted the
dependabot/npm_and_yarn/scripts/browser/playwright-1.55.1
branch
June 14, 2026 17:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps playwright from 1.49.0 to 1.55.1.
Release notes
Sourced from playwright's releases.
... (truncated)
Commits
ae51df7chore: mark v1.55.1 (#37530)86dde29feat(chromium): roll to r1193 (#37529)86328bcchore: do not use -k option (#37532)63799bacherry-pick(#37214): docs: fix method names in release notes21e29a4cherry-pick(#37153): fix(html): don't display a chip with empty content with ...ba62e6acherry-pick(#37149): fix(test): attaching in boxed fixture25bb073cherry-pick(#37137): Revert "fix(a11y): track inert elements as hidden (#36947)"f992162chore: mark v1.55.0 (#37121)4a92ea0cherry-pick(#37113): docs: add release-notes for v1.55aa05507cherry-pick(#37114): test: move browser._launchServer in child processMaintainer changes
This version was pushed to npm by playwright-bot, a new releaser for playwright since your current version.
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)You can disable automated security fix PRs for this repo from the Security Alerts page.