├── .hass └── config │ ├── scenes.yaml │ ├── scripts.yaml │ ├── .HA_VERSION │ ├── automations.yaml │ ├── home-assistant.log │ ├── home-assistant_v2.db │ ├── .storage │ ├── map │ ├── trace.saved_traces │ ├── bluetooth.passive_update_processor │ ├── repairs.issue_registry │ ├── core.uuid │ ├── http.auth │ ├── core.analytics │ ├── lovelace.map │ ├── onboarding │ ├── person │ ├── auth_provider.homeassistant │ ├── lovelace_dashboards │ ├── http │ ├── frontend.user_data_ec313e3c38fb4d60b44e0db03cdc1250 │ ├── core.config │ ├── assist_pipeline.pipelines │ ├── core.area_registry │ ├── core.config_entries │ └── homeassistant.exposed_entities │ ├── secrets.yaml │ ├── known_devices.yaml │ ├── ui-lovelace-header.yaml │ ├── ui-lovelace-account.yaml │ ├── ui-lovelace-overflow.yaml │ ├── ui-lovelace-context-menu.yaml │ ├── ui-lovelace-search.yaml │ ├── ui-lovelace-menu-button.yaml │ ├── ui-lovelace-sidebar.yaml │ ├── ui-lovelace-assistant.yaml │ ├── ui-lovelace-notifications.yaml │ ├── ui-lovelace-overflow-mouse.yaml │ ├── ui-lovelace-mouse.yaml │ ├── ui-lovelace-refresh.yaml │ ├── ui-lovelace-edit-dashboard.yaml │ ├── ui-lovelace-unused-entities.yaml │ ├── ui-lovelace-reload-resources.yaml │ ├── blueprints │ ├── automation │ │ └── homeassistant │ │ │ ├── motion_light.yaml │ │ │ └── notify_leaving_zone.yaml │ └── script │ │ └── homeassistant │ │ └── confirmable_notification.yaml │ ├── home-assistant.log.1 │ ├── inputs │ └── booleans │ │ └── kiosk-mode.yaml │ ├── configuration.yaml │ └── ui-lovelace.yaml ├── .github ├── FUNDING.yml ├── workflows │ ├── hacs.yaml │ ├── ha-beta-tests.yaml │ ├── test.yaml │ └── build.yaml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml └── release-drafter.yml ├── example.png ├── hacs.json ├── images ├── overflow-menu.png ├── lovelace-dashboard.png ├── more-info-dialog-light-entity.png ├── more-info-dialog-media-entity.png ├── more-info-dialog-timer-entity.png ├── more-info-dialog-update-entity.png ├── more-info-dialog-climate-entity.png ├── kiosk-mode-complements │ └── cards │ │ ├── more-info-button.png │ │ └── climate-entities-card-elements.png └── more-info-dialog-history-logbook-attributes.png ├── test-snapshots ├── 01 - main-options.spec.ts │ └── chromium │ │ ├── 01-kiosk.png │ │ ├── 02-hide-header.png │ │ ├── 09-hide-search.png │ │ ├── 03-hide-sidebar.png │ │ ├── 06-hide-account.png │ │ ├── 10-hide-assistant.png │ │ ├── 11-hide-overflow.png │ │ ├── 12-hide-refresh.png │ │ ├── 04-hide-menubutton.png │ │ ├── 05-hide-notifications.png │ │ ├── 15-hide-edit-dashboard.png │ │ ├── 13-hide-unused-entities.png │ │ ├── 14-hide-reload-resources.png │ │ ├── 08-hide-add-to-home-assistant.png │ │ └── 07-hide-notifications-and-hide-account.png ├── 03 - url-parameters.spec.ts │ └── chromium │ │ ├── 01-kiosk.png │ │ ├── 02-hide-header.png │ │ ├── 03-hide-sidebar.png │ │ ├── 06-hide-account.png │ │ ├── 08-hide-search.png │ │ ├── 11-hide-refresh.png │ │ ├── 09-hide-assistant.png │ │ ├── 10-hide-overflow.png │ │ ├── 04-hide-menubutton.png │ │ ├── 05-hide-notifications.png │ │ ├── 14-hide-edit-dashboard.png │ │ ├── 20-hide-dialog-history.png │ │ ├── 21-hide-dialog-logbook.png │ │ ├── 12-hide-unused-entities.png │ │ ├── 13-hide-reload-resources.png │ │ ├── 22-hide-dialog-attributes.png │ │ ├── 07-hide-add-to-home-assistant.png │ │ ├── 16-hide-dialog-header-history.png │ │ ├── 17-hide-dialog-header-settings.png │ │ ├── 18-hide-dialog-header-overflow.png │ │ ├── 23-hide-dialog-update-actions.png │ │ ├── 24-hide-dialog-media-actions.png │ │ ├── 25-hide-dialog-climate-actions.png │ │ ├── 28-hide-dialog-light-actions.png │ │ ├── 32-hide-dialog-timer-actions.png │ │ ├── 33-hide-dialog-history-show-more.png │ │ ├── 34-hide-dialog-logbook-show-more.png │ │ ├── 19-hide-dialog-header-action-items.png │ │ ├── 30-hide-dialog-light-color-actions.png │ │ ├── 29-hide-dialog-light-control-actions.png │ │ ├── 31-hide-dialog-light-settings-actions.png │ │ ├── 27-hide-dialog-climate-settings-actions.png │ │ ├── 26-hide-dialog-climate-temperature-actions.png │ │ └── 15-hide-dialog-header-breadcrumb-navigation.png └── 02 - more-info-dialogs.spec.ts │ └── chromium │ ├── 06-hide-dialog-history.png │ ├── 07-hide-dialog-logbook.png │ ├── 08-hide-dialog-attributes.png │ ├── 02-hide-dialog-header-history.png │ ├── 09-hide-dialog-update-actions.png │ ├── 10-hide-dialog-media-actions.png │ ├── 14-hide-dialog-light-actions.png │ ├── 18-hide-dialog-timer-actions.png │ ├── 03-hide-dialog-header-settings.png │ ├── 04-hide-dialog-header-overflow.png │ ├── 11-hide-dialog-climate-actions.png │ ├── 19-hide-dialog-history-show-more.png │ ├── 20-hide-dialog-logbook-show-more.png │ ├── 05-hide-dialog-header-action-items.png │ ├── 16-hide-dialog-light-color-actions.png │ ├── 15-hide-dialog-light-control-actions.png │ ├── 17-hide-dialog-light-settings-actions.png │ ├── 13-hide-dialog-climate-settings-actions.png │ ├── 12-hide-dialog-climate-temperature-actions.png │ └── 01-hide-dialog-header-breadcrumb-navigation.png ├── .gitignore ├── AUTHORS ├── tsconfig.json ├── eslint.config.js ├── rollup.config.js ├── LICENSE ├── tests ├── utils │ └── index.ts ├── constants │ └── index.ts ├── 01 - main-options.spec.ts ├── 04 - caching.spec.ts ├── 02 - more-info-dialogs.spec.ts └── 03 - url-parameters.spec.ts ├── src ├── types │ └── index.ts ├── console-messenger.ts ├── utilities │ └── index.ts ├── styles │ └── index.ts └── constants │ └── index.ts ├── playwright.config.ts ├── package.json └── KIOSK-MODE-COMPLEMENTS.md /.hass/config/scenes.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.hass/config/scripts.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.hass/config/.HA_VERSION: -------------------------------------------------------------------------------- 1 | 2025.11.0 -------------------------------------------------------------------------------- /.hass/config/automations.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.hass/config/home-assistant.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [NemesisRE] 2 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/example.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kiosk Mode", 3 | "filename": "kiosk-mode.js", 4 | "render_readme": true 5 | } -------------------------------------------------------------------------------- /images/overflow-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/overflow-menu.png -------------------------------------------------------------------------------- /images/lovelace-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/lovelace-dashboard.png -------------------------------------------------------------------------------- /.hass/config/home-assistant_v2.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/.hass/config/home-assistant_v2.db -------------------------------------------------------------------------------- /images/more-info-dialog-light-entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/more-info-dialog-light-entity.png -------------------------------------------------------------------------------- /images/more-info-dialog-media-entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/more-info-dialog-media-entity.png -------------------------------------------------------------------------------- /images/more-info-dialog-timer-entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/more-info-dialog-timer-entity.png -------------------------------------------------------------------------------- /images/more-info-dialog-update-entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/more-info-dialog-update-entity.png -------------------------------------------------------------------------------- /images/more-info-dialog-climate-entity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/more-info-dialog-climate-entity.png -------------------------------------------------------------------------------- /.hass/config/.storage/map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "map", 5 | "data": { 6 | "migrated": true 7 | } 8 | } -------------------------------------------------------------------------------- /.hass/config/.storage/trace.saved_traces: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "trace.saved_traces", 5 | "data": {} 6 | } -------------------------------------------------------------------------------- /images/kiosk-mode-complements/cards/more-info-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/kiosk-mode-complements/cards/more-info-button.png -------------------------------------------------------------------------------- /images/more-info-dialog-history-logbook-attributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/more-info-dialog-history-logbook-attributes.png -------------------------------------------------------------------------------- /.hass/config/.storage/bluetooth.passive_update_processor: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "bluetooth.passive_update_processor", 5 | "data": {} 6 | } -------------------------------------------------------------------------------- /.hass/config/.storage/repairs.issue_registry: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 2, 4 | "key": "repairs.issue_registry", 5 | "data": { 6 | "issues": [] 7 | } 8 | } -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/01-kiosk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/01-kiosk.png -------------------------------------------------------------------------------- /.hass/config/.storage/core.uuid: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "core.uuid", 5 | "data": { 6 | "uuid": "a37f773efd2c4609a1e0e804e8b3137a" 7 | } 8 | } -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/01-kiosk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/01-kiosk.png -------------------------------------------------------------------------------- /.hass/config/.storage/http.auth: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "http.auth", 5 | "data": { 6 | "content_user": "515e09c847c94f65b6915ea512a6c79c" 7 | } 8 | } -------------------------------------------------------------------------------- /.hass/config/secrets.yaml: -------------------------------------------------------------------------------- 1 | # Use this file to store secrets like usernames and passwords. 2 | # Learn more at https://www.home-assistant.io/docs/configuration/secrets/ 3 | some_password: welcome -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/02-hide-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/02-hide-header.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/09-hide-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/09-hide-search.png -------------------------------------------------------------------------------- /images/kiosk-mode-complements/cards/climate-entities-card-elements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/images/kiosk-mode-complements/cards/climate-entities-card-elements.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/03-hide-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/03-hide-sidebar.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/06-hide-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/06-hide-account.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/10-hide-assistant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/10-hide-assistant.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/11-hide-overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/11-hide-overflow.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/12-hide-refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/12-hide-refresh.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/02-hide-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/02-hide-header.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/03-hide-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/03-hide-sidebar.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/06-hide-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/06-hide-account.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/08-hide-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/08-hide-search.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/11-hide-refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/11-hide-refresh.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/04-hide-menubutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/04-hide-menubutton.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/09-hide-assistant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/09-hide-assistant.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/10-hide-overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/10-hide-overflow.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/05-hide-notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/05-hide-notifications.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/15-hide-edit-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/15-hide-edit-dashboard.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/04-hide-menubutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/04-hide-menubutton.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/13-hide-unused-entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/13-hide-unused-entities.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/14-hide-reload-resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/14-hide-reload-resources.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/05-hide-notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/05-hide-notifications.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/14-hide-edit-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/14-hide-edit-dashboard.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/20-hide-dialog-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/20-hide-dialog-history.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/21-hide-dialog-logbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/21-hide-dialog-logbook.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .DS_Store 3 | .hass/config/www/kiosk-mode.js 4 | /.nyc_output 5 | /coverage 6 | /test-results/ 7 | /playwright-report/ 8 | /blob-report/ 9 | /playwright/.cache/ 10 | /.env 11 | -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/06-hide-dialog-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/06-hide-dialog-history.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/07-hide-dialog-logbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/07-hide-dialog-logbook.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/12-hide-unused-entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/12-hide-unused-entities.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/13-hide-reload-resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/13-hide-reload-resources.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/22-hide-dialog-attributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/22-hide-dialog-attributes.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/08-hide-add-to-home-assistant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/08-hide-add-to-home-assistant.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/08-hide-dialog-attributes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/08-hide-dialog-attributes.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/07-hide-add-to-home-assistant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/07-hide-add-to-home-assistant.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/16-hide-dialog-header-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/16-hide-dialog-header-history.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/17-hide-dialog-header-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/17-hide-dialog-header-settings.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/18-hide-dialog-header-overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/18-hide-dialog-header-overflow.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/23-hide-dialog-update-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/23-hide-dialog-update-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/24-hide-dialog-media-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/24-hide-dialog-media-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/25-hide-dialog-climate-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/25-hide-dialog-climate-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/28-hide-dialog-light-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/28-hide-dialog-light-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/32-hide-dialog-timer-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/32-hide-dialog-timer-actions.png -------------------------------------------------------------------------------- /.hass/config/.storage/core.analytics: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "core.analytics", 5 | "data": { 6 | "onboarded": true, 7 | "preferences": {}, 8 | "uuid": null 9 | } 10 | } -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/02-hide-dialog-header-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/02-hide-dialog-header-history.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/09-hide-dialog-update-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/09-hide-dialog-update-actions.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/10-hide-dialog-media-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/10-hide-dialog-media-actions.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/14-hide-dialog-light-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/14-hide-dialog-light-actions.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/18-hide-dialog-timer-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/18-hide-dialog-timer-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/33-hide-dialog-history-show-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/33-hide-dialog-history-show-more.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/34-hide-dialog-logbook-show-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/34-hide-dialog-logbook-show-more.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/03-hide-dialog-header-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/03-hide-dialog-header-settings.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/04-hide-dialog-header-overflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/04-hide-dialog-header-overflow.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/11-hide-dialog-climate-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/11-hide-dialog-climate-actions.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/19-hide-dialog-history-show-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/19-hide-dialog-history-show-more.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/20-hide-dialog-logbook-show-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/20-hide-dialog-logbook-show-more.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/19-hide-dialog-header-action-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/19-hide-dialog-header-action-items.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/30-hide-dialog-light-color-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/30-hide-dialog-light-color-actions.png -------------------------------------------------------------------------------- /test-snapshots/01 - main-options.spec.ts/chromium/07-hide-notifications-and-hide-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/01 - main-options.spec.ts/chromium/07-hide-notifications-and-hide-account.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/05-hide-dialog-header-action-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/05-hide-dialog-header-action-items.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/16-hide-dialog-light-color-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/16-hide-dialog-light-color-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/29-hide-dialog-light-control-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/29-hide-dialog-light-control-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/31-hide-dialog-light-settings-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/31-hide-dialog-light-settings-actions.png -------------------------------------------------------------------------------- /.hass/config/.storage/lovelace.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "lovelace.map", 5 | "data": { 6 | "config": { 7 | "strategy": { 8 | "type": "map" 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/15-hide-dialog-light-control-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/15-hide-dialog-light-control-actions.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/17-hide-dialog-light-settings-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/17-hide-dialog-light-settings-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/27-hide-dialog-climate-settings-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/27-hide-dialog-climate-settings-actions.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/13-hide-dialog-climate-settings-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/13-hide-dialog-climate-settings-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/26-hide-dialog-climate-temperature-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/26-hide-dialog-climate-temperature-actions.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/12-hide-dialog-climate-temperature-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/12-hide-dialog-climate-temperature-actions.png -------------------------------------------------------------------------------- /test-snapshots/03 - url-parameters.spec.ts/chromium/15-hide-dialog-header-breadcrumb-navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/03 - url-parameters.spec.ts/chromium/15-hide-dialog-header-breadcrumb-navigation.png -------------------------------------------------------------------------------- /test-snapshots/02 - more-info-dialogs.spec.ts/chromium/01-hide-dialog-header-breadcrumb-navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NemesisRE/kiosk-mode/HEAD/test-snapshots/02 - more-info-dialogs.spec.ts/chromium/01-hide-dialog-header-breadcrumb-navigation.png -------------------------------------------------------------------------------- /.hass/config/.storage/onboarding: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "minor_version": 1, 4 | "key": "onboarding", 5 | "data": { 6 | "done": [ 7 | "user", 8 | "core_config", 9 | "analytics", 10 | "integration" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /.hass/config/.storage/person: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "minor_version": 1, 4 | "key": "person", 5 | "data": { 6 | "items": [ 7 | { 8 | "id": "test", 9 | "name": "Test", 10 | "user_id": "ec313e3c38fb4d60b44e0db03cdc1250", 11 | "device_trackers": [] 12 | } 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /.hass/config/known_devices.yaml: -------------------------------------------------------------------------------- 1 | 2 | demo_paulus: 3 | name: Paulus 4 | mac: 5 | icon: 6 | picture: 7 | track: true 8 | 9 | demo_anne_therese: 10 | name: Anne Therese 11 | mac: 12 | icon: 13 | picture: 14 | track: true 15 | 16 | demo_home_boy: 17 | name: Home Boy 18 | mac: 19 | icon: 20 | picture: 21 | track: true 22 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-header.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_header: true 3 | title: Kiosk-mode hide header 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Header 7 | path: kiosk-mode-hide-header 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_header` on `true`. 12 | -------------------------------------------------------------------------------- /.hass/config/.storage/auth_provider.homeassistant: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "auth_provider.homeassistant", 5 | "data": { 6 | "users": [ 7 | { 8 | "username": "test", 9 | "password": "JDJiJDEyJGZJR3p2ZWdnd1JtV2txQkl6dlZtUE9MTGdLL1pWVEFNdXhFdkJYeTRXeHhDVm1IeTBZai9D" 10 | } 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-account.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_account: true 3 | title: Kiosk-mode hide account 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Account 7 | path: kiosk-mode-hide-account 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_account` on `true`. The account avatar is located in the bottom of the sidebar. 12 | -------------------------------------------------------------------------------- /.hass/config/.storage/lovelace_dashboards: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "lovelace_dashboards", 5 | "data": { 6 | "items": [ 7 | { 8 | "id": "map", 9 | "icon": "mdi:map", 10 | "title": "Mapa", 11 | "url_path": "map", 12 | "show_in_sidebar": true, 13 | "require_admin": false, 14 | "mode": "storage" 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-overflow.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_overflow: true 3 | title: Kiosk-mode hide overflow 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Overflow 7 | path: kiosk-mode-hide-overflow 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_overflow` on `true`. The overflow menu is located at the right of the header (three-dots button). 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-context-menu.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | block_context_menu: true 3 | title: Kiosk-mode block context menu 4 | views: 5 | - theme: Backend-selected 6 | title: Block Context Menu 7 | path: kiosk-mode-block-context-menu 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `block_context_menu` on `true`. You can not open a context-menu by right-clicking. 12 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of "kiosk mode" significant contributors. 2 | # 3 | # This does not necessarily list everyone who has contributed code. 4 | # To see the full list of contributors, see the revision history in 5 | # source control. 6 | 7 | # Original authors 8 | Mattias Persson <> () 9 | Ryan Meek <> () 10 | # Maintainer 11 | Iván Pereira <> () 12 | -------------------------------------------------------------------------------- /.hass/config/.storage/http: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "http", 5 | "data": { 6 | "cors_allowed_origins": [ 7 | "https://cast.home-assistant.io" 8 | ], 9 | "ip_ban_enabled": true, 10 | "server_host": [ 11 | "0.0.0.0", 12 | "::" 13 | ], 14 | "server_port": 8123, 15 | "use_x_frame_options": true, 16 | "ssl_profile": "modern", 17 | "login_attempts_threshold": -1 18 | } 19 | } -------------------------------------------------------------------------------- /.hass/config/.storage/frontend.user_data_ec313e3c38fb4d60b44e0db03cdc1250: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "frontend.user_data_ec313e3c38fb4d60b44e0db03cdc1250", 5 | "data": { 6 | "language": { 7 | "language": "en", 8 | "number_format": "language", 9 | "time_format": "language", 10 | "date_format": "language", 11 | "time_zone": "local", 12 | "first_weekday": "language" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-search.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_search: true 3 | title: Kiosk-mode hide search 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Search 7 | path: kiosk-mode-hide-search 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_search` on `true`. The search icon is located at right of the header or inside the overflow menu (top-right corner) on small screens. 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-menu-button.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_menubutton: true 3 | title: Kiosk-mode hide menu button 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Menu Button 7 | path: kiosk-mode-hide-menu-button 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_menubutton` on `true`. The menu button is located on the top-left and it is used to open and close the sidebar. 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-sidebar.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_sidebar: true 3 | title: Kiosk-mode hide sidebar 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Sidebar 7 | path: kiosk-mode-hide-sidebar 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_sidebar` on `true`. To be able to select another dashboard from the sidebar you would need to add `?disable_km` at the end of the URL. 12 | -------------------------------------------------------------------------------- /.github/workflows/hacs.yaml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | schedule: 11 | - cron: "0 0 * * *" 12 | 13 | jobs: 14 | hacs: 15 | name: HACS Action 16 | runs-on: "ubuntu-latest" 17 | steps: 18 | - uses: "actions/checkout@v6" 19 | - name: HACS Action 20 | uses: "hacs/action@main" 21 | with: 22 | category: "plugin" -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-assistant.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_assistant: true 3 | title: Kiosk-mode hide assistant 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Search 7 | path: kiosk-mode-hide-assistant 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_assistant` on `true`. The assistant icon is located at right of the header or inside the overflow menu (top-right corner) on small screens. 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-notifications.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_notifications: true 3 | title: Kiosk-mode hide notifications 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Notifications 7 | path: kiosk-mode-hide-notifications 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_notifications` on `true`. The notifications entry-point is located in the bottom of the sidebar, above the account avatar. 12 | -------------------------------------------------------------------------------- /.hass/config/.storage/core.config: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 4, 4 | "key": "core.config", 5 | "data": { 6 | "latitude": 52.3731339, 7 | "longitude": 4.8903147, 8 | "elevation": 0, 9 | "unit_system_v2": "metric", 10 | "location_name": "Home", 11 | "time_zone": "Europe/Amsterdam", 12 | "external_url": null, 13 | "internal_url": null, 14 | "currency": "EUR", 15 | "country": "NL", 16 | "language": "es", 17 | "radius": 100 18 | } 19 | } -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-overflow-mouse.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | block_overflow: true 3 | title: Kiosk-mode block overflow 4 | views: 5 | - theme: Backend-selected 6 | title: Block Overflow 7 | path: kiosk-mode-block-overflow 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `block_overflow` on `true`. You cannot interact with the overflow menu if this option is enabled. The overflow menu is located at the right of the header (three-dots button). 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-mouse.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | block_mouse: true 3 | title: Kiosk-mode block mouse 4 | views: 5 | - theme: Backend-selected 6 | title: Block Mouse 7 | path: kiosk-mode-block-mouse 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `block_mouse` on `true`. You cannot interact without any element inside home assistant and your mouse will not be visible. The only way of get rid of this behaviour is to place `?disable_km` at the end of the URL. 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-refresh.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_refresh: true 3 | title: Kiosk-mode hide refresh 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Refresh 7 | path: kiosk-mode-hide-refresh 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_refresh` on `true`. The refresh button is located inside the overflow menu (top-right corner) in [yaml mode](https://www.home-assistant.io/dashboards/dashboards/#using-yaml-for-the-default-dashboard). 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-edit-dashboard.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_edit_dashboard: true 3 | title: Kiosk-mode hide edit dashboard 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Edit Dashboard 7 | path: kiosk-mode-hide-edit-dashboard 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_edit_dashboard` on `true`. The edit dashboard button is located inside the overflow menu (top-right corner) in [yaml mode](https://www.home-assistant.io/dashboards/dashboards/#using-yaml-for-the-default-dashboard). 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-unused-entities.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_unused_entities: true 3 | title: Kiosk-mode hide unused entities 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Refresh 7 | path: kiosk-mode-hide-unused-entities 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_unused_entities` on `true`. The unused entities button is located inside the overflow menu (top-right corner) in [yaml mode](https://www.home-assistant.io/dashboards/dashboards/#using-yaml-for-the-default-dashboard). 12 | -------------------------------------------------------------------------------- /.hass/config/ui-lovelace-reload-resources.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | hide_reload_resources: true 3 | title: Kiosk-mode hide reload resources 4 | views: 5 | - theme: Backend-selected 6 | title: Hide Reload Resources 7 | path: kiosk-mode-hide-reload-resources 8 | badges: [] 9 | cards: 10 | - type: markdown 11 | content: This dashboard has the setting `hide_reload_resources` on `true`. The reload resources button is located inside the overflow menu (top-right corner) in [yaml mode](https://www.home-assistant.io/dashboards/dashboards/#using-yaml-for-the-default-dashboard). 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "module": "esnext", 5 | "target": "ES5", 6 | "moduleResolution": "node", 7 | "types": ["node"], 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true, 10 | "declaration": false, 11 | "noImplicitAny": true, 12 | "removeComments": true, 13 | "baseUrl": "./src", 14 | "paths": { 15 | "@types": ["types"], 16 | "@constants": ["constants"], 17 | "@utilities": ["utilities"], 18 | "@styles": ["styles"] 19 | } 20 | }, 21 | "include": ["src/**/*.ts", "package.json"], 22 | "exclude": ["node_modules"] 23 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FR] " 5 | labels: "✨ feature-request" 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.hass/config/.storage/assist_pipeline.pipelines: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 2, 4 | "key": "assist_pipeline.pipelines", 5 | "data": { 6 | "items": [ 7 | { 8 | "conversation_engine": "conversation.home_assistant", 9 | "conversation_language": "en", 10 | "id": "01hfsxav88enscr7b5majn3ak6", 11 | "language": "en", 12 | "name": "Home Assistant", 13 | "stt_engine": null, 14 | "stt_language": null, 15 | "tts_engine": "google_translate", 16 | "tts_language": "en-us", 17 | "tts_voice": null, 18 | "wake_word_entity": null, 19 | "wake_word_id": null 20 | } 21 | ], 22 | "preferred_item": "01hfsxav88enscr7b5majn3ak6" 23 | } 24 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: "\U0001F41B bug" 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | - Console log errors (F12) 13 | 14 | **To Reproduce** 15 | Steps to reproduce the behavior: 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Versions:** 28 | - Home Assistant: [e.g. 2023.5.4] 29 | - Kiosk-Mode [e.g. v1.9.2] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "sunday" 8 | time: "09:00" 9 | commit-message: 10 | prefix: "[Dependencies]" 11 | groups: 12 | dependencies-prod: 13 | dependency-type: "production" 14 | dependencies-dev: 15 | dependency-type: "development" 16 | labels: 17 | - dependencies 18 | versioning-strategy: increase 19 | - package-ecosystem: "github-actions" 20 | directory: "/" 21 | schedule: 22 | interval: "weekly" 23 | day: "sunday" 24 | time: "09:00" 25 | timezone: "Europe/Amsterdam" 26 | commit-message: 27 | prefix: "[Github Actions]" 28 | groups: 29 | actions-deps: 30 | patterns: 31 | - "*" 32 | labels: 33 | - configuration 34 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const tseslint = require('typescript-eslint'); 2 | const js = require('@eslint/js'); 3 | const globals = require('globals'); 4 | 5 | module.exports = [ 6 | { 7 | languageOptions: { 8 | globals: { 9 | Atomics: 'readonly', 10 | SharedArrayBuffer: 'readonly', 11 | ...globals.browser, 12 | ...globals.node, 13 | ...globals.es2015 14 | } 15 | } 16 | }, 17 | js.configs.recommended, 18 | ...tseslint.configs.recommended, 19 | { 20 | rules: { 21 | quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }], 22 | indent: ['error', 'tab'], 23 | semi: ['error', 'always'], 24 | 'no-trailing-spaces': ['error'], 25 | '@typescript-eslint/no-duplicate-enum-values': 'off', 26 | '@typescript-eslint/no-var-requires': 'off' 27 | } 28 | }, 29 | { 30 | files: ['**/*.js'], 31 | rules: { 32 | '@typescript-eslint/no-require-imports': 'off' 33 | } 34 | } 35 | ]; -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import ts from 'rollup-plugin-ts'; 2 | import json from '@rollup/plugin-json'; 3 | import terser from '@rollup/plugin-terser'; 4 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 5 | import istanbul from 'rollup-plugin-istanbul'; 6 | 7 | export default [ 8 | { 9 | plugins: [ 10 | nodeResolve(), 11 | json(), 12 | ts({ 13 | browserslist: false 14 | }), 15 | terser({ 16 | output: { 17 | comments: false 18 | } 19 | }) 20 | ], 21 | input: 'src/kiosk-mode.ts', 22 | output: { 23 | file: 'dist/kiosk-mode.js', 24 | format: 'iife' 25 | } 26 | }, 27 | { 28 | plugins: [ 29 | nodeResolve(), 30 | json(), 31 | ts({ 32 | browserslist: false 33 | }), 34 | istanbul({ 35 | exclude: [ 36 | 'node_modules/**/*', 37 | 'package.json' 38 | ] 39 | }) 40 | ], 41 | input: 'src/kiosk-mode.ts', 42 | output: { 43 | file: '.hass/config/www/kiosk-mode.js', 44 | format: 'iife' 45 | } 46 | } 47 | ]; -------------------------------------------------------------------------------- /.github/workflows/ha-beta-tests.yaml: -------------------------------------------------------------------------------- 1 | name: Home Assistant Beta Nightly Tests 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | beta-tests: 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v6 16 | - name: Install pnpm 17 | uses: pnpm/action-setup@v4 18 | with: 19 | version: 10 20 | run_install: false 21 | - name: Set up Node.js 22 | uses: actions/setup-node@v6 23 | with: 24 | node-version: 20 25 | cache: 'pnpm' 26 | - name: Install 27 | run: pnpm install 28 | - name: E2E Tests 29 | run: TAG=beta pnpm test:ci 30 | - name: Create coverage 31 | run: pnpm coverage:report 32 | - uses: actions/upload-artifact@v6 33 | if: always() 34 | with: 35 | name: test-report 36 | path: | 37 | playwright-report/ 38 | coverage/ 39 | retention-days: 30 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ryan Meek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v6 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v4 19 | with: 20 | version: 10 21 | run_install: false 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v6 24 | with: 25 | node-version: 20 26 | cache: 'pnpm' 27 | - name: Install 28 | run: pnpm install 29 | - name: E2E tests 30 | run: pnpm test:all 31 | - name: Create coverage 32 | run: pnpm coverage:report 33 | - name: Coveralls 34 | uses: coverallsapp/github-action@master 35 | with: 36 | github-token: ${{ secrets.GITHUB_TOKEN }} 37 | - uses: actions/upload-artifact@v6 38 | if: always() 39 | with: 40 | name: test-report 41 | path: | 42 | playwright-report/ 43 | coverage/ 44 | retention-days: 30 -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - '🌟 feature' 7 | - '✨ feature-request' 8 | - 'enhancement' 9 | - 'request' 10 | - title: '🛠 Fixes' 11 | labels: 12 | - 'fix' 13 | - '🐛 bug' 14 | - title: '🧩 Dependencies' 15 | labels: 16 | - 'dependencies' 17 | - title: '⚙️ Configuration' 18 | labels: 19 | - 'configuration' 20 | - title: '📝 Documentation' 21 | labels: 22 | - '📝 documentation' 23 | - title: '📦 Other' 24 | labels: [] 25 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 26 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 27 | version-resolver: 28 | major: 29 | labels: 30 | - 'major' 31 | minor: 32 | labels: 33 | - '🌟 feature' 34 | - '✨ feature-request' 35 | - 'enhancement' 36 | - 'request' 37 | - 'minor' 38 | patch: 39 | labels: 40 | - 'fix' 41 | - '🐛 bug' 42 | - '🧩 dependencies' 43 | - 'dependencies' 44 | - 'patch' 45 | default: patch 46 | template: | 47 | ## Changes 48 | 49 | $CHANGES -------------------------------------------------------------------------------- /tests/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { expect } from 'playwright-test-coverage'; 3 | import { BASE_URL, SELECTORS } from '../constants'; 4 | 5 | interface Context { 6 | id: string; 7 | user_id: string; 8 | } 9 | 10 | interface HomeAssistant extends HTMLElement { 11 | hass: { 12 | callService: (domain: string, service: string, data: Record) => Promise; 13 | }; 14 | } 15 | 16 | export const goToPage = async (page: Page) => { 17 | await page.goto('/'); 18 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 19 | }; 20 | 21 | export const goToPageWithParams = async (page: Page, ...params: string[]) => { 22 | await page.goto( 23 | getUrlWithParam(...params) 24 | ); 25 | await expect(page.locator('hui-view')).toBeVisible(); 26 | }; 27 | 28 | export const turnBooleanState = async ( 29 | page: Page, 30 | entity: string, 31 | state: boolean 32 | ) => { 33 | await page.evaluate(async ({ entity, state }) => { 34 | const homeAssistant = document.querySelector('home-assistant') as HomeAssistant; 35 | await homeAssistant.hass.callService( 36 | 'input_boolean', 37 | `turn_${state ? 'on' : 'off'}`, 38 | { 39 | entity_id: `input_boolean.${entity}` 40 | } 41 | ); 42 | }, { entity, state }); 43 | await page.waitForTimeout(100); 44 | }; 45 | 46 | export const getUrlWithParam = (...params: string[]) => `${BASE_URL}?${params.join('&')}`; -------------------------------------------------------------------------------- /.hass/config/blueprints/automation/homeassistant/motion_light.yaml: -------------------------------------------------------------------------------- 1 | blueprint: 2 | name: Motion-activated Light 3 | description: Turn on a light when motion is detected. 4 | domain: automation 5 | source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml 6 | author: Home Assistant 7 | input: 8 | motion_entity: 9 | name: Motion Sensor 10 | selector: 11 | entity: 12 | filter: 13 | device_class: motion 14 | domain: binary_sensor 15 | light_target: 16 | name: Light 17 | selector: 18 | target: 19 | entity: 20 | domain: light 21 | no_motion_wait: 22 | name: Wait time 23 | description: Time to leave the light on after last motion is detected. 24 | default: 120 25 | selector: 26 | number: 27 | min: 0 28 | max: 3600 29 | unit_of_measurement: seconds 30 | 31 | # If motion is detected within the delay, 32 | # we restart the script. 33 | mode: restart 34 | max_exceeded: silent 35 | 36 | trigger: 37 | platform: state 38 | entity_id: !input motion_entity 39 | from: "off" 40 | to: "on" 41 | 42 | action: 43 | - alias: "Turn on the light" 44 | service: light.turn_on 45 | target: !input light_target 46 | - alias: "Wait until there is no motion from device" 47 | wait_for_trigger: 48 | platform: state 49 | entity_id: !input motion_entity 50 | from: "on" 51 | to: "off" 52 | - alias: "Wait the number of seconds that has been set" 53 | delay: !input no_motion_wait 54 | - alias: "Turn off the light" 55 | service: light.turn_off 56 | target: !input light_target 57 | -------------------------------------------------------------------------------- /.hass/config/.storage/core.area_registry: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 8, 4 | "key": "core.area_registry", 5 | "data": { 6 | "areas": [ 7 | { 8 | "aliases": [], 9 | "floor_id": null, 10 | "humidity_entity_id": null, 11 | "icon": null, 12 | "id": "woonkamer", 13 | "labels": [], 14 | "name": "Woonkamer", 15 | "picture": null, 16 | "temperature_entity_id": null, 17 | "created_at": "1970-01-01T00:00:00+00:00", 18 | "modified_at": "1970-01-01T00:00:00+00:00" 19 | }, 20 | { 21 | "aliases": [], 22 | "floor_id": null, 23 | "humidity_entity_id": null, 24 | "icon": null, 25 | "id": "keuken", 26 | "labels": [], 27 | "name": "Keuken", 28 | "picture": null, 29 | "temperature_entity_id": null, 30 | "created_at": "1970-01-01T00:00:00+00:00", 31 | "modified_at": "1970-01-01T00:00:00+00:00" 32 | }, 33 | { 34 | "aliases": [], 35 | "floor_id": null, 36 | "humidity_entity_id": null, 37 | "icon": null, 38 | "id": "slaapkamer", 39 | "labels": [], 40 | "name": "Slaapkamer", 41 | "picture": null, 42 | "temperature_entity_id": null, 43 | "created_at": "1970-01-01T00:00:00+00:00", 44 | "modified_at": "1970-01-01T00:00:00+00:00" 45 | }, 46 | { 47 | "aliases": [], 48 | "floor_id": null, 49 | "humidity_entity_id": null, 50 | "icon": null, 51 | "id": "kelder", 52 | "labels": [], 53 | "name": "Kelder", 54 | "picture": null, 55 | "temperature_entity_id": null, 56 | "created_at": "2025-07-04T19:31:13.322435+00:00", 57 | "modified_at": "2025-07-04T19:31:13.322440+00:00" 58 | } 59 | ] 60 | } 61 | } -------------------------------------------------------------------------------- /.hass/config/blueprints/automation/homeassistant/notify_leaving_zone.yaml: -------------------------------------------------------------------------------- 1 | blueprint: 2 | name: Zone Notification 3 | description: Send a notification to a device when a person leaves a specific zone. 4 | domain: automation 5 | source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml 6 | author: Home Assistant 7 | input: 8 | person_entity: 9 | name: Person 10 | selector: 11 | entity: 12 | filter: 13 | domain: person 14 | zone_entity: 15 | name: Zone 16 | selector: 17 | entity: 18 | filter: 19 | domain: zone 20 | notify_device: 21 | name: Device to notify 22 | description: Device needs to run the official Home Assistant app to receive notifications. 23 | selector: 24 | device: 25 | filter: 26 | integration: mobile_app 27 | 28 | trigger: 29 | platform: state 30 | entity_id: !input person_entity 31 | 32 | variables: 33 | zone_entity: !input zone_entity 34 | # This is the state of the person when it's in this zone. 35 | zone_state: "{{ states[zone_entity].name }}" 36 | person_entity: !input person_entity 37 | person_name: "{{ states[person_entity].name }}" 38 | 39 | condition: 40 | condition: template 41 | # The first case handles leaving the Home zone which has a special state when zoning called 'home'. 42 | # The second case handles leaving all other zones. 43 | value_template: "{{ zone_entity == 'zone.home' and trigger.from_state.state == 'home' and trigger.to_state.state != 'home' or trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" 44 | 45 | action: 46 | - alias: "Notify that a person has left the zone" 47 | domain: mobile_app 48 | type: notify 49 | device_id: !input notify_device 50 | message: "{{ person_name }} has left {{ zone_state }}" 51 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { HomeAssistant, Hass } from 'home-assistant-javascript-templates'; 2 | import { 3 | OPTION, 4 | CONDITIONAL_OPTION, 5 | DEBUG_CONFIG_OPTION 6 | } from '@constants'; 7 | 8 | export interface KioskModeRunner { 9 | run: (lovelace: HTMLElement) => Promise; 10 | runDialogs: (dialog: Element) => void; 11 | } 12 | 13 | export interface MobileSettings extends Exclude { 14 | custom_width: number; 15 | } 16 | 17 | export interface UserSetting extends ConditionalConfig { 18 | users: string[]; 19 | } 20 | 21 | type BaseKioskConfig = Partial< 22 | Record< 23 | OPTION, 24 | boolean | string 25 | > 26 | >; 27 | 28 | type DebugKioskConfig = Partial< 29 | Record< 30 | DEBUG_CONFIG_OPTION, 31 | boolean | string 32 | > 33 | >; 34 | 35 | type BaseConditionalKioskConfig = Partial< 36 | Record< 37 | CONDITIONAL_OPTION, 38 | boolean | string 39 | > 40 | >; 41 | 42 | export type Options = Partial< 43 | Record< 44 | OPTION | CONDITIONAL_OPTION | DEBUG_CONFIG_OPTION, 45 | boolean 46 | > 47 | >; 48 | 49 | export interface KioskConfig extends BaseKioskConfig, BaseConditionalKioskConfig, DebugKioskConfig { 50 | admin_settings?: ConditionalConfig; 51 | non_admin_settings?: ConditionalConfig; 52 | user_settings?: UserSetting[]; 53 | mobile_settings?: MobileSettings; 54 | } 55 | 56 | export type ConditionalConfig = BaseKioskConfig & BaseConditionalKioskConfig; 57 | 58 | export interface HomeAsssistantExtended extends HomeAssistant { 59 | hass: Hass & { 60 | config: { 61 | version: string; 62 | }; 63 | localize: (path: string) => string; 64 | panelUrl: string; 65 | }; 66 | } 67 | 68 | export class Lovelace extends HTMLElement { 69 | lovelace: { 70 | config: { 71 | kiosk_mode: KioskConfig; 72 | }; 73 | mode: string; 74 | }; 75 | } 76 | 77 | export class HaSidebar extends HTMLElement { 78 | type: 'modal' | ''; 79 | appContent: HTMLElement; 80 | } 81 | 82 | export interface SubscriberTemplate { 83 | result: string; 84 | } 85 | 86 | export interface MoreInfoDialog extends HTMLElement { 87 | open: boolean; 88 | } 89 | 90 | declare global { 91 | interface Window { 92 | KioskMode: KioskModeRunner; 93 | } 94 | } 95 | 96 | export type Version = [number, number, string]; -------------------------------------------------------------------------------- /.hass/config/home-assistant.log.1: -------------------------------------------------------------------------------- 1 | 2025-05-04 19:04:48.164 WARNING (Recorder) [homeassistant.components.recorder.util] The system could not validate that the sqlite3 database at //config/home-assistant_v2.db was shutdown cleanly 2 | 2025-05-04 19:04:49.324 WARNING (Recorder) [homeassistant.components.recorder.migration] The database is about to upgrade from schema version 47 to 50 3 | 2025-05-04 19:04:49.324 WARNING (Recorder) [homeassistant.components.recorder.migration] Upgrading recorder db schema to version 48 4 | 2025-05-04 19:04:49.340 WARNING (Recorder) [homeassistant.components.recorder.migration] Upgrade to version 48 done 5 | 2025-05-04 19:04:49.340 WARNING (Recorder) [homeassistant.components.recorder.migration] Upgrading recorder db schema to version 49 6 | 2025-05-04 19:04:49.340 WARNING (Recorder) [homeassistant.components.recorder.migration] Adding columns mean_type to table statistics_meta. Note: this may take several minutes on large databases and slow machines. Please be patient! 7 | 2025-05-04 19:04:49.348 WARNING (Recorder) [homeassistant.components.recorder.migration] Adding columns mean_weight to table statistics. Note: this may take several minutes on large databases and slow machines. Please be patient! 8 | 2025-05-04 19:04:49.351 WARNING (Recorder) [homeassistant.components.recorder.migration] Adding columns mean_weight to table statistics_short_term. Note: this may take several minutes on large databases and slow machines. Please be patient! 9 | 2025-05-04 19:04:49.376 WARNING (Recorder) [homeassistant.components.recorder.migration] Upgrade to version 49 done 10 | 2025-05-04 19:04:49.376 WARNING (Recorder) [homeassistant.components.recorder.migration] Upgrading recorder db schema to version 50 11 | 2025-05-04 19:04:49.414 WARNING (Recorder) [homeassistant.components.recorder.migration] Upgrade to version 50 done 12 | 2025-05-04 19:04:52.575 WARNING (MainThread) [homeassistant.components.http.ban] Login attempt or request with invalid authentication from 172.17.0.1 (172.17.0.1). Requested URL: '/api/websocket'. (Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36) 13 | 2025-05-04 19:04:52.804 WARNING (MainThread) [homeassistant.components.http.ban] Login attempt or request with invalid authentication from 172.17.0.1 (172.17.0.1). Requested URL: '/api/websocket'. (Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36) 14 | -------------------------------------------------------------------------------- /.hass/config/inputs/booleans/kiosk-mode.yaml: -------------------------------------------------------------------------------- 1 | kiosk: 2 | name: Kiosk mode 3 | kiosk_hide_header: 4 | name: Kiosk hide header 5 | kiosk_hide_sidebar: 6 | name: Kiosk hide sidebar 7 | kiosk_hide_menubutton: 8 | name: Kiosk hide menu button 9 | kiosk_hide_overflow: 10 | name: Kiosk hide overflow 11 | kiosk_hide_account: 12 | name: Kiosk hide account 13 | kiosk_hide_notifications: 14 | name: Kiosk hide notifications 15 | kiosk_hide_add_to_home_assistant: 16 | name: Kiosk hide add to Home Assistant 17 | kiosk_hide_search: 18 | name: Kiosk hide search 19 | kiosk_hide_assistant: 20 | name: Kiosk hide assistant 21 | kiosk_hide_refresh: 22 | name: Kiosk hide refresh 23 | kiosk_hide_unused_entities: 24 | name: Kiosk hide unused entities 25 | kiosk_hide_reload_resources: 26 | name: Kiosk hide reload resources 27 | kiosk_hide_edit_dashboard: 28 | name: Kiosk hide edit dashboard 29 | kiosk_hide_dialog_header_breadcrumb_navigation: 30 | name: Kiosk hide dialog header breadcrumb navigation 31 | kiosk_hide_dialog_header_history: 32 | name: Kiosk hide dialog header history 33 | kiosk_hide_dialog_header_settings: 34 | name: Kiosk hide dialog header settings 35 | kiosk_hide_dialog_header_overflow: 36 | name: Kiosk hide dialog header overflow 37 | kiosk_hide_dialog_header_action_items: 38 | name: Kiosk hide header action items 39 | kiosk_hide_dialog_history: 40 | name: Kiosk hide dialog history 41 | kiosk_hide_dialog_logbook: 42 | name: Kiosk hide dialog logbook 43 | kiosk_hide_dialog_attributes: 44 | name: Kiosk hide dialog attributes 45 | kiosk_hide_dialog_update_actions: 46 | name: Kiosk hide dialog update actions 47 | kiosk_hide_dialog_camera_actions: 48 | name: Kiosk hide dialog camera actions 49 | kiosk_hide_dialog_media_actions: 50 | name: Kiosk hide dialog media actions 51 | kiosk_hide_dialog_timer_actions: 52 | name: Kiosk hide dialog timer actions 53 | kiosk_hide_dialog_climate_actions: 54 | name: Kiosk hide dialog climate actions 55 | kiosk_hide_dialog_climate_temperature_actions: 56 | name: Kiosk hide dialog climate temperature actions 57 | kiosk_hide_dialog_climate_settings_actions: 58 | name: Kiosk hide dialog climate settings actions 59 | kiosk_hide_dialog_light_actions: 60 | name: Kiosk hide dialog light actions 61 | kiosk_hide_dialog_light_control_actions: 62 | name: Kiosk hide dialog light control actions 63 | kiosk_hide_dialog_light_color_actions: 64 | name: Kiosk hide dialog light color actions 65 | kiosk_hide_dialog_light_settings_actions: 66 | name: Kiosk hide dialog light settings actions 67 | kiosk_hide_dialog_history_show_more: 68 | name: Kiosk hide dialog history show more link 69 | kiosk_hide_dialog_logbook_show_more: 70 | name: Kiosk hide dialog logbook show more link 71 | kiosk_block_overflow: 72 | name: Kiosk block overflow 73 | kiosk_block_mouse: 74 | name: Kiosk block mouse 75 | kiosk_block_context_menu: 76 | name: Kiosk block context menu 77 | -------------------------------------------------------------------------------- /.hass/config/.storage/core.config_entries: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 5, 4 | "key": "core.config_entries", 5 | "data": { 6 | "entries": [ 7 | {"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"sun","entry_id":"be26a900e88a562e5489f3bd7d023692","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"import","subentries":[],"title":"Sun","unique_id":null,"version":1}, 8 | {"created_at":"1970-01-01T00:00:00+00:00","data":{"language":"en","tld":"com"},"disabled_by":null,"discovery_keys":{},"domain":"google_translate","entry_id":"2b569b49288de83eef7651b79d83ed63","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","subentries":[],"title":"Google Translate text-to-speech","unique_id":null,"version":1}, 9 | {"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"shopping_list","entry_id":"780cc0ac75c927dd75bd8de858b61f74","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","subentries":[],"title":"Shopping list","unique_id":"shopping_list","version":1}, 10 | {"created_at":"1970-01-01T00:00:00+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"radio_browser","entry_id":"94025d24627372e146c71929c4b15076","minor_version":1,"modified_at":"1970-01-01T00:00:00+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"onboarding","subentries":[],"title":"Radio Browser","unique_id":null,"version":1}, 11 | {"created_at":"2025-01-08T00:13:15.278369+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"demo","entry_id":"01JH1M48MENVGG2FGTF5AVGM57","minor_version":1,"modified_at":"2025-01-08T00:13:15.278372+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"import","subentries":[],"title":"Demo","unique_id":null,"version":1}, 12 | {"created_at":"2025-01-08T00:13:15.394994+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"go2rtc","entry_id":"01JH1M48R21C8R1FHZBRN5WNR8","minor_version":1,"modified_at":"2025-01-08T00:13:15.394996+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"system","subentries":[],"title":"go2rtc","unique_id":null,"version":1}, 13 | {"created_at":"2025-05-04T19:04:49.327218+00:00","data":{},"disabled_by":null,"discovery_keys":{},"domain":"backup","entry_id":"01JTEAZKDF03B0J87536SD2VHQ","minor_version":1,"modified_at":"2025-05-04T19:04:49.327220+00:00","options":{},"pref_disable_new_entities":false,"pref_disable_polling":false,"source":"system","subentries":[],"title":"Backup","unique_id":null,"version":1} 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /.hass/config/configuration.yaml: -------------------------------------------------------------------------------- 1 | # Loads default set of integrations. Do not remove. 2 | default_config: 3 | 4 | homeassistant: 5 | auth_providers: 6 | - type: trusted_networks 7 | trusted_networks: 8 | - 0.0.0.0/0 9 | allow_bypass_login: true 10 | 11 | # Text to speech 12 | tts: 13 | - platform: google_translate 14 | 15 | automation: !include automations.yaml 16 | script: !include scripts.yaml 17 | scene: !include scenes.yaml 18 | 19 | lovelace: 20 | mode: yaml 21 | dashboards: 22 | lovelace-header: 23 | mode: yaml 24 | title: Kiosk-mode hide header 25 | filename: ui-lovelace-header.yaml 26 | lovelace-account: 27 | mode: yaml 28 | title: Kiosk-mode hide account 29 | filename: ui-lovelace-account.yaml 30 | lovelace-notifications: 31 | mode: yaml 32 | title: Kiosk-mode hide notifications 33 | filename: ui-lovelace-notifications.yaml 34 | lovelace-menu-button: 35 | mode: yaml 36 | title: Kiosk-mode hide menu button 37 | filename: ui-lovelace-menu-button.yaml 38 | lovelace-sidebar: 39 | mode: yaml 40 | title: Kiosk-mode hide sidebar 41 | filename: ui-lovelace-sidebar.yaml 42 | lovelace-search: 43 | mode: yaml 44 | title: Kiosk-mode hide search 45 | filename: ui-lovelace-search.yaml 46 | lovelace-assistant: 47 | mode: yaml 48 | title: Kiosk-mode hide assistant 49 | filename: ui-lovelace-assistant.yaml 50 | lovelace-refresh: 51 | mode: yaml 52 | title: Kiosk-mode hide refresh 53 | filename: ui-lovelace-refresh.yaml 54 | lovelace-unused-entities: 55 | mode: yaml 56 | title: Kiosk-mode hide unused entities 57 | filename: ui-lovelace-unused-entities.yaml 58 | lovelace-reload-resources: 59 | mode: yaml 60 | title: Kiosk-mode hide reload resources 61 | filename: ui-lovelace-reload-resources.yaml 62 | lovelace-edit-dashboard: 63 | mode: yaml 64 | title: Kiosk-mode hide edit dashboard 65 | filename: ui-lovelace-edit-dashboard.yaml 66 | lovelace-overflow: 67 | mode: yaml 68 | title: Kiosk-mode hide overflow 69 | filename: ui-lovelace-overflow.yaml 70 | lovelace-block-overflow: 71 | mode: yaml 72 | title: Kiosk-mode block overflow 73 | filename: ui-lovelace-overflow-mouse.yaml 74 | lovelace-mouse: 75 | mode: yaml 76 | title: Kiosk-mode block mouse 77 | filename: ui-lovelace-mouse.yaml 78 | lovelace-context-menu: 79 | mode: yaml 80 | title: Kiosk-mode block context menu 81 | filename: ui-lovelace-context-menu.yaml 82 | 83 | # Input booleans 84 | input_boolean: !include_dir_merge_named inputs/booleans/ 85 | 86 | # Load frontend themes from the themes folder 87 | frontend: 88 | themes: !include_dir_merge_named themes 89 | extra_module_url: /local/kiosk-mode.js 90 | 91 | timer: 92 | laundry: 93 | duration: "00:01:00" 94 | 95 | demo: -------------------------------------------------------------------------------- /.hass/config/blueprints/script/homeassistant/confirmable_notification.yaml: -------------------------------------------------------------------------------- 1 | blueprint: 2 | name: Confirmable Notification 3 | description: >- 4 | A script that sends an actionable notification with a confirmation before 5 | running the specified action. 6 | domain: script 7 | source_url: https://github.com/home-assistant/core/blob/master/homeassistant/components/script/blueprints/confirmable_notification.yaml 8 | author: Home Assistant 9 | input: 10 | notify_device: 11 | name: Device to notify 12 | description: Device needs to run the official Home Assistant app to receive notifications. 13 | selector: 14 | device: 15 | filter: 16 | integration: mobile_app 17 | title: 18 | name: "Title" 19 | description: "The title of the button shown in the notification." 20 | default: "" 21 | selector: 22 | text: 23 | message: 24 | name: "Message" 25 | description: "The message body" 26 | selector: 27 | text: 28 | confirm_text: 29 | name: "Confirmation Text" 30 | description: "Text to show on the confirmation button" 31 | default: "Confirm" 32 | selector: 33 | text: 34 | confirm_action: 35 | name: "Confirmation Action" 36 | description: "Action to run when notification is confirmed" 37 | default: [] 38 | selector: 39 | action: 40 | dismiss_text: 41 | name: "Dismiss Text" 42 | description: "Text to show on the dismiss button" 43 | default: "Dismiss" 44 | selector: 45 | text: 46 | dismiss_action: 47 | name: "Dismiss Action" 48 | description: "Action to run when notification is dismissed" 49 | default: [] 50 | selector: 51 | action: 52 | 53 | mode: restart 54 | 55 | sequence: 56 | - alias: "Set up variables" 57 | variables: 58 | action_confirm: "{{ 'CONFIRM_' ~ context.id }}" 59 | action_dismiss: "{{ 'DISMISS_' ~ context.id }}" 60 | - alias: "Send notification" 61 | domain: mobile_app 62 | type: notify 63 | device_id: !input notify_device 64 | title: !input title 65 | message: !input message 66 | data: 67 | actions: 68 | - action: "{{ action_confirm }}" 69 | title: !input confirm_text 70 | - action: "{{ action_dismiss }}" 71 | title: !input dismiss_text 72 | - alias: "Awaiting response" 73 | wait_for_trigger: 74 | - platform: event 75 | event_type: mobile_app_notification_action 76 | event_data: 77 | action: "{{ action_confirm }}" 78 | - platform: event 79 | event_type: mobile_app_notification_action 80 | event_data: 81 | action: "{{ action_dismiss }}" 82 | - choose: 83 | - conditions: "{{ wait.trigger.event.data.action == action_confirm }}" 84 | sequence: !input confirm_action 85 | - conditions: "{{ wait.trigger.event.data.action == action_dismiss }}" 86 | sequence: !input dismiss_action 87 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | /** 4 | * Read environment variables from file. 5 | * https://github.com/motdotla/dotenv 6 | */ 7 | // require('dotenv').config(); 8 | 9 | /** 10 | * See https://playwright.dev/docs/test-configuration. 11 | */ 12 | export default defineConfig({ 13 | testDir: './tests', 14 | /* Run tests in files in parallel */ 15 | fullyParallel: false, 16 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 17 | forbidOnly: !!process.env.CI, 18 | /* Retry on CI only */ 19 | retries: process.env.CI ? 3 : 0, 20 | /* Opt out of parallel tests on CI. */ 21 | workers: 1, 22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 23 | reporter: [ 24 | ['list'], 25 | ['github'], 26 | ['html', { open: 'never' }] 27 | ], 28 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 29 | use: { 30 | /* Base URL to use in actions like `await page.goto('/')`. */ 31 | baseURL: 'http://host.docker.internal:8123', 32 | 33 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 34 | trace: 'on-first-retry', 35 | screenshot: 'only-on-failure' 36 | }, 37 | expect: { 38 | timeout: 15000, 39 | toHaveScreenshot: { 40 | maxDiffPixelRatio: 0.15, 41 | threshold: 0.5, 42 | animations: 'disabled' 43 | } 44 | }, 45 | snapshotDir: 'test-snapshots', 46 | snapshotPathTemplate: '{snapshotDir}/{testFilePath}/{projectName}/{arg}{ext}', 47 | // Update snapshots 48 | // updateSnapshots: 'all', 49 | /* Configure projects for major browsers */ 50 | projects: [ 51 | { 52 | name: 'chromium', 53 | use: { 54 | ...devices['Desktop Chrome'], 55 | launchOptions: { 56 | args: [ 57 | '--font-render-hinting=none', 58 | '--disable-skia-runtime-opts', 59 | '--disable-font-subpixel-positioning', 60 | '--disable-lcd-text', 61 | ] 62 | } 63 | }, 64 | }, 65 | // { 66 | // name: 'firefox', 67 | // use: { ...devices['Desktop Firefox'] }, 68 | // }, 69 | // { 70 | // name: 'webkit', 71 | // use: { ...devices['Desktop Safari'] }, 72 | // }, 73 | /* Test against mobile viewports. */ 74 | // { 75 | // name: 'Mobile Chrome', 76 | // use: { ...devices['Pixel 5'] }, 77 | // }, 78 | // { 79 | // name: 'Mobile Safari', 80 | // use: { ...devices['iPhone 12'] }, 81 | // }, 82 | 83 | /* Test against branded browsers. */ 84 | // { 85 | // name: 'Microsoft Edge', 86 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 87 | // }, 88 | // { 89 | // name: 'Google Chrome', 90 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 91 | // }, 92 | ], 93 | 94 | /* Run your local dev server before starting the tests */ 95 | // webServer: { 96 | // command: 'npm run start', 97 | // url: 'http://127.0.0.1:3000', 98 | // reuseExistingServer: !process.env.CI, 99 | // }, 100 | }); 101 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | permissions: 3 | contents: read 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | tags: 10 | - 'v[0-9]+.[0-9]+.[0-9]+' 11 | - 'v[0-9]+.[0-9]+.[0-9]+-beta' 12 | - 'v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' 13 | - 'v[0-9]+.[0-9]+.[0-9]+-alpha' 14 | - 'v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+' 15 | - 'v[0-9]+.[0-9]+.[0-9]+-rc' 16 | - 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+' 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-22.04 21 | permissions: 22 | contents: write 23 | pull-requests: write 24 | steps: 25 | - name: Check Event Type 26 | id: check-event 27 | run: | 28 | if [[ ${{ github.ref_type }} == "tag" ]]; then 29 | echo "EVENT_TYPE=tag" >> $GITHUB_OUTPUT 30 | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+-.+([0-9]+)?$ ]]; then 31 | echo "PRERELEASE=true" >> $GITHUB_OUTPUT 32 | else 33 | echo "PRERELEASE=false" >> $GITHUB_OUTPUT 34 | fi 35 | echo "TARGET_COMMITISH=${{ github.sha }}" >> $GITHUB_OUTPUT 36 | else 37 | echo "EVENT_TYPE=push" >> $GITHUB_OUTPUT 38 | echo "PRERELEASE=false" >> $GITHUB_OUTPUT 39 | echo "TARGET_COMMITISH=${{ github.event.repository.default_branch }}" >> $GITHUB_OUTPUT 40 | fi 41 | - name: Checkout 42 | uses: actions/checkout@v6 43 | - name: Install pnpm 44 | uses: pnpm/action-setup@v4 45 | with: 46 | version: 10 47 | run_install: false 48 | - name: Set-up Node 49 | uses: actions/setup-node@v6 50 | with: 51 | node-version: 20 52 | cache: 'pnpm' 53 | - name: Install deps 54 | run: pnpm install 55 | - name: E2E tests 56 | run: pnpm test:all 57 | - name: Create coverage 58 | run: pnpm coverage:report 59 | - uses: actions/upload-artifact@v6 60 | if: always() 61 | with: 62 | name: test-report 63 | path: | 64 | playwright-report/ 65 | coverage/ 66 | retention-days: 30 67 | - name: Update draft release 68 | if: steps.check-event.outputs.EVENT_TYPE == 'push' 69 | uses: release-drafter/release-drafter@v6 70 | with: 71 | config-name: release-drafter.yml 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | - name: Create tagged release 75 | if: steps.check-event.outputs.EVENT_TYPE == 'tag' 76 | uses: release-drafter/release-drafter@v6 77 | with: 78 | config-name: release-drafter.yml 79 | publish: true 80 | tag: ${{ github.ref_name }} 81 | name: ${{ github.ref_name }} 82 | prerelease: ${{ steps.check-event.outputs.PRERELEASE }} 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | - name: Upload release assets 86 | if: steps.check-event.outputs.EVENT_TYPE == 'tag' 87 | uses: softprops/action-gh-release@v2 88 | with: 89 | tag_name: ${{ github.ref_name }} 90 | files: | 91 | dist/kiosk-mode.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kiosk-mode", 3 | "version": "9.0.2", 4 | "description": "Hides the Home Assistant header and/or sidebar", 5 | "main": "kiosk-mode.js", 6 | "repository": "git@github.com:NemesisRE/kiosk-mode.git", 7 | "author": "Steven \"NemesisRE\" Koeberich ", 8 | "license": "MIT", 9 | "scripts": { 10 | "build": "rollup --config rollup.config.js --bundleConfigAsCjs", 11 | "lint": "eslint \"src/**/*.{js,ts}\" \"tests/**/*.ts\"", 12 | "lint:fix": "pnpm lint --fix", 13 | "test:ts": "tsc --noEmit", 14 | "test:tag": "pnpm reset:ha && pnpm demo && pnpm tag:playwright && pnpm stop:ha", 15 | "test:clean": "rm -rf dist .nyc_output coverage || true", 16 | "start:playwright": "docker run --rm --network host --add-host host.docker.internal:host-gateway -v $(pwd):$(pwd) -w $(pwd) -i mcr.microsoft.com/playwright:v$(jq -r '.devDependencies[\"@playwright/test\"]' package.json)-jammy sh -c \"npx playwright test\"", 17 | "tag:playwright": "docker run --rm --network host --add-host host.docker.internal:host-gateway -v $(pwd):/$(pwd)/ -w $(pwd) -i mcr.microsoft.com/playwright:v$(jq -r '.devDependencies[\"@playwright/test\"]' package.json)-jammy sh -c \"npx playwright test --grep @testing\"", 18 | "test:open": "playwright test --ui", 19 | "test:update": "playwright test --update-snapshots", 20 | "test:ci": "pnpm test:clean && pnpm demo && pnpm start:playwright && pnpm stop:ha", 21 | "test:all": "pnpm lint && pnpm test:ts && pnpm test:ci", 22 | "coverage:report": "nyc report --reporter=lcov --reporter=text-summary", 23 | "start:ha": "docker run --rm -d -p8123:8123 --shm-size=512m -v ${PWD}/.hass/config:/config homeassistant/home-assistant:${TAG:-$(cat .hass/config/.HA_VERSION)}", 24 | "start:ha:win": "docker run --rm -d -p8123:8123 --shm-size=512m -v %cd%/.hass/config:/config homeassistant/home-assistant:${TAG:-$(cat .hass/config/.HA_VERSION)}", 25 | "stop:ha": "docker stop $(docker ps -a -q --filter ancestor=homeassistant/home-assistant:${TAG:-$(cat .hass/config/.HA_VERSION)}) || true", 26 | "reset:ha": "git add .hass/config/.HA_VERSION && git checkout .hass/config", 27 | "demo": "pnpm build && pnpm start:ha", 28 | "demo:win": "pnpm build && pnpm start:ha:win", 29 | "prepare": "pnpm build", 30 | "prepublishOnly": "pnpm test:all", 31 | "version": "git add .", 32 | "postversion": "git push && git push --tags" 33 | }, 34 | "dependencies": { 35 | "get-promisable-result": "^1.0.1", 36 | "home-assistant-query-selector": "4.3.0", 37 | "home-assistant-javascript-templates": "6.0.0", 38 | "home-assistant-styles-manager": "3.1.0" 39 | }, 40 | "devDependencies": { 41 | "@playwright/test": "1.57.0", 42 | "@rollup/plugin-json": "^6.1.0", 43 | "@rollup/plugin-node-resolve": "^16.0.3", 44 | "@rollup/plugin-terser": "^0.4.4", 45 | "@types/node": "^25.0.3", 46 | "@types/sinon": "^21.0.0", 47 | "eslint": "9.39.2", 48 | "globals": "^16.5.0", 49 | "nyc": "^17.1.0", 50 | "playwright-test-coverage": "^1.2.12", 51 | "rollup": "^4.54.0", 52 | "rollup-plugin-istanbul": "^5.0.0", 53 | "rollup-plugin-ts": "^3.4.5", 54 | "sinon": "^21.0.1", 55 | "tslib": "^2.8.1", 56 | "typescript": "^5.9.3", 57 | "typescript-eslint": "^8.50.0" 58 | }, 59 | "pnpm": { 60 | "overrides": { 61 | "js-yaml@<4.1.1": ">=4.1.1" 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/console-messenger.ts: -------------------------------------------------------------------------------- 1 | import { KioskConfig } from '@types'; 2 | import { NAMESPACE, CONDITIONAL_OPTION } from '@constants'; 3 | import { getCSSString } from 'home-assistant-styles-manager'; 4 | import { version } from '../package.json'; 5 | 6 | interface Line { 7 | content: string; 8 | color?: string; 9 | background?: string; 10 | } 11 | 12 | const MAX_LENGTH = 27; 13 | const LINES: Line[] = [ 14 | { 15 | content: '%c≡ kiosk-mode', 16 | color: 'white', 17 | background: '#03a9f4' 18 | }, 19 | { 20 | content: `%cversion ${version}` 21 | } 22 | ]; 23 | 24 | const CONSOLE_STYLES = { 25 | NORMAL: 'font-weight: normal; color: inherit;', 26 | BLUE: 'font-weight: bold; color: blue;', 27 | RED: 'font-weight: bold; color: red;', 28 | GREEN: 'font-weight: bold; color: green;', 29 | CODE: 'color: #666', 30 | UNDERLINE: 'text-decoration: underline' 31 | }; 32 | 33 | const CONDITIONAL_OPTION_VALUES = Object.values(CONDITIONAL_OPTION); 34 | 35 | export class ConsoleMessenger { 36 | 37 | public static logInfo() { 38 | const contents: string[] = []; 39 | const styles: string[] = []; 40 | const lastIndex = LINES.length - 1; 41 | const commonStyles = { 42 | 'border-color' : '#424242', 43 | 'border-style' : 'solid', 44 | 'display' : 'inline-block', 45 | 'font-family' : 'monospace', 46 | 'font-size' : '12px' 47 | }; 48 | 49 | LINES.forEach((line: Line, index: number): void => { 50 | 51 | // contents 52 | contents.push(line.content.padEnd(MAX_LENGTH)); 53 | contents.push('%c⋮'); 54 | 55 | if (index !== lastIndex) contents.push('%c\n'); 56 | 57 | // styles 58 | let borderWidthStart = '0 0 0 1px'; 59 | let borderWidthEnd = '0 1px 0 1px'; 60 | 61 | if (index === 0) { 62 | borderWidthStart = '1px 0 0 1px'; 63 | borderWidthEnd = '1px 1px 0 0'; 64 | } else if (index === lastIndex) { 65 | borderWidthStart = '0 0 1px 1px'; 66 | borderWidthEnd = '0 1px 1px 0'; 67 | } 68 | 69 | styles.push( 70 | getCSSString({ 71 | ...commonStyles, 72 | 'background' : line.background || 'white', 73 | 'color' : line.color || '#424242', 74 | 'padding' : index === 0 75 | ? '1px 0px 1px 5px' 76 | : '1px 0px 1px 10px', 77 | 'border-width' : borderWidthStart, 78 | }) 79 | ); 80 | 81 | styles.push( 82 | getCSSString({ 83 | ...commonStyles, 84 | 'background' : line.background || 'white', 85 | 'color' : line.color || 'white', 86 | 'padding' : index === 0 87 | ? '1px 5px' 88 | : '1px 5px 1px 0px', 89 | 'border-width' : borderWidthEnd, 90 | }) 91 | ); 92 | 93 | if (index !== lastIndex) styles.push(''); 94 | 95 | }); 96 | 97 | console.info(contents.join(''), ...styles); 98 | 99 | } 100 | 101 | public static debugRawConfig(config: KioskConfig, panel: string): void { 102 | console.groupCollapsed(`${NAMESPACE} raw config for ${panel} panel`); 103 | console.table(config); 104 | console.groupEnd(); 105 | } 106 | 107 | public static debugFinalConfig(config: KioskConfig, panel: string): void { 108 | const entries = Object.entries(config); 109 | const filteredConfig = entries.filter(([prop]) => !(prop in CONDITIONAL_OPTION_VALUES)); 110 | console.groupCollapsed(`${NAMESPACE} final config for ${panel} panel`); 111 | console.table( 112 | Object.fromEntries(filteredConfig) 113 | ); 114 | console.groupEnd(); 115 | } 116 | 117 | public static debug(option: string, code: string, result: unknown): void { 118 | console.groupCollapsed(`${NAMESPACE} debug`); 119 | console.info( 120 | `Template rendering triggered for option %c${option}%c with the next template:`, 121 | CONSOLE_STYLES.BLUE, 122 | CONSOLE_STYLES.NORMAL 123 | ); 124 | console.info( 125 | `%c${code}`, 126 | CONSOLE_STYLES.CODE 127 | ); 128 | console.info( 129 | `The evaluation of this template is: %c${result}`, 130 | typeof result === 'boolean' 131 | ? CONSOLE_STYLES.GREEN 132 | : CONSOLE_STYLES.RED 133 | ); 134 | if (typeof result !== 'boolean') { 135 | console.warn( 136 | '%c⚠️ As this template didn\'t return a boolean, this option has been set to false', 137 | CONSOLE_STYLES.UNDERLINE 138 | ); 139 | } 140 | console.groupEnd(); 141 | } 142 | 143 | public static debugTemplate(code: string, result: unknown): void { 144 | console.group(`${NAMESPACE} template debug`); 145 | console.info(`The template debug has been triggered with the next template:`); 146 | console.info( 147 | `%c${code}`, 148 | CONSOLE_STYLES.CODE 149 | ); 150 | console.info( 151 | `The evaluation of this template is: %c${result}`, 152 | typeof result === 'boolean' 153 | ? CONSOLE_STYLES.GREEN 154 | : CONSOLE_STYLES.RED 155 | ); 156 | if (typeof result !== 'boolean') { 157 | console.warn( 158 | '%c⚠️ This template doesn\'t return a boolean. It cannot be used as a kiosk-mode option.', 159 | CONSOLE_STYLES.UNDERLINE 160 | ); 161 | } 162 | console.groupEnd(); 163 | } 164 | 165 | } -------------------------------------------------------------------------------- /KIOSK-MODE-COMPLEMENTS.md: -------------------------------------------------------------------------------- 1 | # kiosk-mode complements 2 | 3 | Some features are outside `kiosk-mode` scope and they would be hard to maintain and escalate over time, but they could be achieved by alternative methods. This document contains community-driven solutions to achieve things that cannot be done with `kiosk-mode` but could be a complement for Home Assistan instances using this plugin. 4 | 5 | ### Main UI 6 | 7 | `kiosk-mode` is intended to hide elements from the UI to avoid non-authorized users accessing Home Assistant config features specially when it is being shown in a device in kiosk mode. But anything related to change the style of the UI or improve it design falls out of the scope of the plugin. This section contains multiple modifications that could be done in elements of the main UI that are out of the scope of `kiosk-mode`. 8 | 9 | #### Move the header to the bottom 10 | 11 | This method moves the main Home Assistant header from the top to the bottom of the page. You need to use the [Card Mod Themes] from [card-mod]. 12 | 13 | ```yaml 14 | your-custom-theme: 15 | card-mod-theme: your-custom-theme 16 | card-mod-root-yaml: | 17 | .: | 18 | .header { 19 | bottom: 0; 20 | top: auto !important; 21 | } 22 | #view { 23 | padding-bottom: calc(var(--header-height) + env(safe-area-inset-top)) !important; 24 | padding-top: 0 !important; 25 | } 26 | ha-tabs$: | 27 | #selectionBar { 28 | bottom: auto; 29 | top: 0; 30 | } 31 | ``` 32 | 33 | #### Align header tabs to the center 34 | 35 | This method aligns the Home Assistant header tabs to the center. You need to use the [Card Mode Themes] from [card-mod]. 36 | 37 | ```yaml 38 | your-custom-theme: 39 | card-mod-theme: your-custom-theme 40 | card-mod-root-yaml: | 41 | .: | 42 | .toolbar ha-tab-group { 43 | display: flex; 44 | justify-content: center; 45 | } 46 | ``` 47 | 48 | #### Hide back button on subviews 49 | 50 | This method hides the back button that appears before the title on lovelace subviews. You need to use the [Card Mode Themes] from [card-mod]. 51 | 52 | ```yaml 53 | your-custom-theme: 54 | card-mod-theme: your-custom-theme 55 | card-mod-root-yaml: | 56 | .header: | 57 | .toolbar ha-icon-button-arrow-prev { 58 | display: none !important; 59 | } 60 | ``` 61 | 62 | ### More-info dialogs 63 | 64 | This section contains multiple modifications that could be done in elements that are inside more-info dialogs. 65 | 66 | #### Hide the header 67 | 68 | With `kiosk-mode` it is possible to hide multiple elements from the more-info dialogs header, but the header itself, containing the title and the button to close the dialog remains there. This method hides the more-info dialogs header altogeteher. You need to use the [Card Mod Themes] from [card-mod]. 69 | 70 | ```yaml 71 | your-custom-theme: 72 | card-mod-theme: your-custom-theme 73 | card-mod-more-info: | 74 | ha-dialog-header { 75 | display: none; 76 | } 77 | ``` 78 | 79 | #### Hide or unhide more-info dialogs attributes 80 | 81 | With `kiosk-mode` it is possible to hide the more-info dialogs attributes, but if one doesn‘t want to hide this element, but hide specific options shown in this element, then `kiosk-mode` is not the right complement for that. For that you can use [custom-attributes](https://github.com/Mariusthvdb/custom-attributes) which allows one to manage which attributes to hide or shown per entity id, domain, device class or device id globs. 82 | 83 | ### Cards 84 | 85 | `kiosk-mode` do not modify cards. Mainly because the layout of a lovelace dashboard could have infinite possibilities and it depends on how the user has built it, it is not a fixed layout as the UI of Home Assistant. On top of that, cards change constantly and there are tons of custom-made cards, it will be impossible to maintain and escalate a code that tries to modify whatever card users have in their dashboards. This section contains multiple modifications that you can achieve on cards. 86 | 87 | 88 | #### Hide more-info button on some native Home Assistant cards 89 | 90 | This method uses [card-mod] to hide the `more-info` button located in the top-right corner of some native Home Assistant cards. This button opens a more-info dialog once it is pressed. 91 | 92 | ![more-info button](images/kiosk-mode-complements/cards/more-info-button.png) 93 | 94 | ```yaml 95 | type: light 96 | entity: light.eetkamer_lamp 97 | card_mod: 98 | ## Hide more-info button 99 | style: | 100 | ha-icon-button.more-info { 101 | display: none; 102 | } 103 | ``` 104 | 105 | #### Hide different elements in native climate-entities Home Assistant cards 106 | 107 | This method uses [card-mod] to hide different elements inside native climate entities Home Assistant cards. 108 | 109 | ![climate entities elements](images/kiosk-mode-complements/cards/climate-entities-card-elements.png) 110 | 111 | ```yaml 112 | type: thermostat 113 | entity: climate.thermostat 114 | name: Woonkamer 115 | card_mod: 116 | style: 117 | ## hide temperature slider 118 | round-slider$: | 119 | .slider { 120 | pointer-events: none; 121 | } 122 | .handles { 123 | display: none 124 | } 125 | ## hide buttons 126 | '#info': | 127 | #modes { 128 | display: none; 129 | } 130 | ``` 131 | 132 | [card-mod]: https://github.com/thomasloven/lovelace-card-mod 133 | [Card Mod Themes]: https://github.com/thomasloven/lovelace-card-mod/wiki/Card-mod-Themes -------------------------------------------------------------------------------- /src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | import { HomeAsssistantExtended, Version } from '@types'; 2 | import { getPromisableResult } from 'get-promisable-result'; 3 | import { 4 | TRUE, 5 | MENU, 6 | MENU_REFERENCES, 7 | ELEMENT, 8 | OPTION, 9 | MAX_ATTEMPTS, 10 | RETRY_DELAY, 11 | RESOURCE_WITH_SUFFIX_REGEXP 12 | } from '@constants'; 13 | 14 | // Get cache key 15 | export const getCacheKey = (option: string): string => { 16 | const optionCamelCase = option.replace(/(?:^|_)([a-z])/g, (__match: string, letter): string => { 17 | return letter.toUpperCase(); 18 | }); 19 | return `km${optionCamelCase}`; 20 | }; 21 | 22 | // Return true if keyword is found in query strings. 23 | export const queryString = (...keywords: string[]): boolean => { 24 | const params = new URLSearchParams(window.location.search); 25 | return keywords.some((x) => params.has(x)); 26 | }; 27 | 28 | // Set localStorage item. 29 | export const setCache = (value: string, ...options: OPTION[]): void => { 30 | options.forEach( 31 | (option) => window.localStorage.setItem( 32 | getCacheKey(option), 33 | value 34 | ) 35 | ); 36 | }; 37 | 38 | // Retrieve localStorage item as bool. 39 | export const cached = (...options: OPTION[]): boolean => { 40 | return options.some((option) => { 41 | return window.localStorage.getItem(getCacheKey(option)) === TRUE; 42 | }); 43 | }; 44 | 45 | // Reset all cache items to false 46 | export const resetCache = () => { 47 | Object.values(OPTION).forEach((option: OPTION): void => { 48 | window.localStorage.removeItem( 49 | getCacheKey(option) 50 | ); 51 | }); 52 | }; 53 | 54 | export const getDisplayNoneRules = (...rules: string[]): Record => { 55 | return Object.fromEntries( 56 | rules.map((rule: string): [string, false] => [rule, false]) 57 | ); 58 | }; 59 | 60 | export const getMenuTranslations = async(ha: HomeAsssistantExtended, version: Version): Promise> => { 61 | 62 | const referencePaths = Object.entries(MENU_REFERENCES); 63 | 64 | // This code is needed for Home Assistant versions older than 2025.9.x 65 | const isLegacy = ( 66 | version[0] < 2025 67 | || ( 68 | version[0] === 2025 69 | && version[1] < 9 70 | ) 71 | ); 72 | const finalReferencePaths = isLegacy 73 | ? referencePaths.filter(([, key]) => { 74 | // This translation doesn't exist in Home Assistant < 2025.9.x 75 | return MENU_REFERENCES[MENU.ADD] !== key; 76 | }) 77 | : referencePaths; 78 | // End of custom code for Home Assistant versions older than 2025.9.x 79 | 80 | const translations = await getPromisableResult( 81 | () => finalReferencePaths.map((entry): [string, string] => { 82 | const [key, translationPath] = entry; 83 | return [ha.hass.localize(translationPath), key]; 84 | }), 85 | (translationEntries: [string, string][]): boolean => { 86 | return !translationEntries.find((entry) => !entry[0]); 87 | }, 88 | { 89 | retries: MAX_ATTEMPTS, 90 | delay: RETRY_DELAY 91 | } 92 | ); 93 | 94 | return Object.fromEntries(translations); 95 | }; 96 | 97 | const getTranslationWithoutShorcutSuffix = (text: string): string => { 98 | return text 99 | .trim() 100 | .replace(RESOURCE_WITH_SUFFIX_REGEXP, '$1') 101 | .trim(); 102 | }; 103 | 104 | export const addMenuItemsDataSelectors = ( 105 | haIconButtons: NodeListOf, 106 | translations: Record 107 | ): void => { 108 | haIconButtons.forEach((haIconButton: HTMLElement): void => { 109 | if ( 110 | haIconButton && 111 | haIconButton.dataset && 112 | !haIconButton.dataset.selector 113 | ) { 114 | const labelledBy = haIconButton.getAttribute('aria-labelledby'); 115 | if (!labelledBy) return; 116 | const tooltip = haIconButton.parentElement.querySelector(`#${labelledBy.trim()}`); 117 | if (!tooltip) return; 118 | const translation = getTranslationWithoutShorcutSuffix(tooltip.textContent); 119 | haIconButton.dataset.selector = translations[translation]; 120 | } 121 | }); 122 | }; 123 | 124 | export const addHeaderButtonsDataSelectors = ( 125 | headerButtons: NodeListOf, 126 | translations: Record 127 | ) => { 128 | headerButtons.forEach((headerButton: HTMLElement): void => { 129 | const menuItem: HTMLElement | null = headerButton.querySelector(ELEMENT.MENU_ITEM); 130 | if ( 131 | menuItem && 132 | menuItem.dataset && 133 | !menuItem.dataset.selector 134 | ) { 135 | const labelledBy = menuItem.getAttribute('aria-labelledby'); 136 | if (labelledBy) { 137 | const tooltip = headerButton.parentElement.querySelector(`#${labelledBy.trim()}`); 138 | if (!tooltip) return; 139 | const translation = getTranslationWithoutShorcutSuffix(tooltip.textContent); 140 | menuItem.dataset.selector = translations[translation]; 141 | } else { 142 | const icon = menuItem.shadowRoot.querySelector(ELEMENT.MENU_ITEM_ICON); 143 | if (icon.title) { 144 | menuItem.dataset.selector = translations[icon.title.trim()]; 145 | } else { 146 | const button = icon.shadowRoot.querySelector('button'); 147 | menuItem.dataset.selector = translations[button.getAttribute('aria-label').trim()]; 148 | } 149 | } 150 | } 151 | }); 152 | }; 153 | 154 | export const addDialogsMenuItemsDataSelectors = ( 155 | menuItems: NodeListOf, 156 | translations: Record 157 | ) => { 158 | menuItems.forEach((menuItem: HTMLElement): void => { 159 | if ( 160 | menuItem && 161 | menuItem.dataset && 162 | !menuItem.dataset.selector 163 | ) { 164 | const icon = menuItem.shadowRoot.querySelector(ELEMENT.MENU_ITEM_ICON); 165 | menuItem.dataset.selector = translations[icon.title.trim()]; 166 | } 167 | }); 168 | }; 169 | 170 | export const addOverlayMenuItemsDataSelectors = ( 171 | overflowMenuItems: NodeListOf, 172 | translations: Record 173 | ) => { 174 | overflowMenuItems.forEach((overflowMenuItem: HTMLElement): void => { 175 | if ( 176 | overflowMenuItem && 177 | overflowMenuItem.dataset && 178 | !overflowMenuItem.dataset.selector 179 | ) { 180 | const textContent = getTranslationWithoutShorcutSuffix(overflowMenuItem.textContent); 181 | overflowMenuItem.dataset.selector = translations[textContent]; 182 | } 183 | }); 184 | }; 185 | 186 | export const parseVersion = (version: string | undefined): Version | null => { 187 | const versionRegExp = /^(\d+)\.(\d+)\.(\w+)(?:\.(\w+))?$/; 188 | const match = version 189 | ? version.match(versionRegExp) 190 | : null; 191 | if (match) { 192 | return [ 193 | +match[1], 194 | +match[2], 195 | match[3] 196 | ]; 197 | } 198 | return null; 199 | }; -------------------------------------------------------------------------------- /src/styles/index.ts: -------------------------------------------------------------------------------- 1 | import { MENU, ELEMENT } from '@constants'; 2 | import { getDisplayNoneRules } from '@utilities'; 3 | 4 | export const STYLES = { 5 | HEADER: { 6 | '#view': { 7 | minHeight: '100vh !important', 8 | KioskHeaderHeight: '0px', 9 | paddingTop: 'calc(var(--kiosk-header-height) + env(safe-area-inset-top)) !important' 10 | }, 11 | '.header': false 12 | }, 13 | ACCOUNT: getDisplayNoneRules('ha-md-list-item.user'), 14 | NOTIFICATIONS: getDisplayNoneRules('ha-md-list-item.notifications'), 15 | DIVIDER: getDisplayNoneRules('.divider'), 16 | SIDEBAR_ITEMS_CONTAINER: ( 17 | hideMenuButton: boolean, 18 | hideNotifications: boolean, 19 | hideAccount: boolean 20 | ) => { 21 | const menuButtonHeight = 56; 22 | const notificationsHeight = 48; 23 | const accountHeight = 50; 24 | let sizeMinimized = 132; 25 | let sizeExpanded = 132; 26 | if (hideAccount && hideNotifications) { 27 | sizeMinimized = 0; 28 | sizeExpanded = 0; 29 | } else if (hideAccount) { 30 | sizeMinimized -= accountHeight; 31 | sizeExpanded -= accountHeight; 32 | } else if (hideNotifications) { 33 | sizeMinimized -= notificationsHeight; 34 | sizeExpanded -= notificationsHeight; 35 | } 36 | if (hideMenuButton) { 37 | sizeMinimized -= menuButtonHeight; 38 | } 39 | return { 40 | ':host([expanded]) .title': { 41 | marginLeft: hideMenuButton 42 | ? '19px' 43 | : '3px' 44 | }, 45 | ':host(:not([expanded])) ha-md-list.ha-scrollbar': { 46 | height: `calc(100% - var(--header-height) - ${sizeMinimized}px - env(safe-area-inset-bottom)) !important` 47 | }, 48 | ':host([expanded]) ha-md-list.ha-scrollbar': { 49 | height: `calc(100% - var(--header-height) - ${sizeExpanded}px - env(safe-area-inset-bottom)) !important` 50 | } 51 | }; 52 | }, 53 | MENU_BUTTON: getDisplayNoneRules( 54 | ':host(:not([expanded])) .menu', 55 | ':host([expanded]) .menu ha-icon-button' 56 | ), 57 | MENU_BUTTON_BURGER: getDisplayNoneRules('ha-menu-button'), 58 | MOUSE: { 59 | 'body::after': { 60 | bottom: 0, 61 | content: '""', 62 | cursor: 'none', 63 | display: 'block', 64 | left: 0, 65 | position: 'fixed', 66 | right: 0, 67 | top: 0, 68 | zIndex: 999999 69 | } 70 | }, 71 | SIDEBAR: { 72 | ':host': { 73 | MdcDrawerWidth: '0px !important', 74 | KioskSidebarWidth: '0px' 75 | }, 76 | 'partial-panel-resolver': { 77 | MdcTopAppBarWidth: '100% !important' 78 | }, 79 | 'ha-drawer > ha-sidebar': false, 80 | '.header': { 81 | width: '100% !important' 82 | } 83 | }, 84 | ASIDE: getDisplayNoneRules('.mdc-drawer'), 85 | OVERFLOW_MENU: getDisplayNoneRules( 86 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.MENU_ITEM}[data-selector="${MENU.OVERFLOW}"]` 87 | ), 88 | BLOCK_OVERFLOW: { 89 | [`${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU}`]: { 90 | 'pointer-events': 'none !important' 91 | } 92 | }, 93 | ADD_TO_HOME_ASSISTANT: getDisplayNoneRules( 94 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.MENU_ITEM}[data-selector="${MENU.ADD}"]`, 95 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.OVERLAY_MENU_ITEM}[data-selector="${MENU.ADD}"]` 96 | ), 97 | SEARCH: getDisplayNoneRules( 98 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.MENU_ITEM}[data-selector="${MENU.SEARCH}"]`, 99 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.OVERLAY_MENU_ITEM}[data-selector="${MENU.SEARCH}"]` 100 | ), 101 | ASSISTANT: getDisplayNoneRules( 102 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.MENU_ITEM}[data-selector="${MENU.ASSIST}"]`, 103 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.OVERLAY_MENU_ITEM}[data-selector="${MENU.ASSIST}"]` 104 | ), 105 | REFRESH: getDisplayNoneRules( 106 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.OVERLAY_MENU_ITEM}[data-selector="${MENU.REFRESH}"]` 107 | ), 108 | UNUSED_ENTITIES: getDisplayNoneRules( 109 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.OVERLAY_MENU_ITEM}[data-selector="${MENU.UNUSED_ENTITIES}"]`, 110 | ), 111 | RELOAD_RESOURCES: getDisplayNoneRules( 112 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.OVERLAY_MENU_ITEM}[data-selector="${MENU.RELOAD_RESOURCES}"]` 113 | ), 114 | EDIT_DASHBOARD: getDisplayNoneRules( 115 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.MENU_ITEM}[data-selector="${MENU.EDIT_DASHBOARD}"]`, 116 | `${ELEMENT.TOOLBAR} > ${ELEMENT.ACTION_ITEMS} > ${ELEMENT.BUTTON_MENU} > ${ELEMENT.OVERLAY_MENU_ITEM}[data-selector="${MENU.EDIT_DASHBOARD}"]` 117 | ), 118 | DIALOG_HEADER_BREADCRUMB_NAVIGATION: getDisplayNoneRules( 119 | `${ELEMENT.HA_DIALOG_HEADER} > .title > .breadcrumb` 120 | ), 121 | DIALOG_HEADER_HISTORY: getDisplayNoneRules( 122 | `${ELEMENT.HA_DIALOG_HEADER} > ${ELEMENT.MENU_ITEM}[data-selector="${MENU.DIALOG_HISTORY}"]` 123 | ), 124 | DIALOG_HEADER_SETTINGS: getDisplayNoneRules( 125 | `${ELEMENT.HA_DIALOG_HEADER} > ${ELEMENT.MENU_ITEM}[data-selector="${MENU.DIALOG_SETTINGS}"]` 126 | ), 127 | DIALOG_HEADER_OVERFLOW: getDisplayNoneRules( 128 | `${ELEMENT.HA_DIALOG_HEADER} > ${ELEMENT.BUTTON_MENU}` 129 | ), 130 | DIALOG_HISTORY: getDisplayNoneRules( 131 | ELEMENT.HA_DIALOG_HISTORY 132 | ), 133 | DIALOG_LOGBOOK: getDisplayNoneRules( 134 | ELEMENT.HA_DIALOG_LOGBOOK 135 | ), 136 | DIALOG_ATTRIBUTES: getDisplayNoneRules( 137 | ELEMENT.HA_DIALOG_ATTRIBUTES 138 | ), 139 | DIALOG_MEDIA_ACTIONS: getDisplayNoneRules( 140 | '.bottom-controls > :is(.main-controls, .controls-row)' 141 | ), 142 | DIALOG_TIMER_ACTIONS: getDisplayNoneRules('.actions'), 143 | DIALOG_UPDATE_ACTIONS: getDisplayNoneRules( 144 | '.actions', 145 | 'ha-md-list:has(+ .actions)', 146 | 'hr:has(+ .actions)' 147 | ), 148 | DIALOG_CAMERA_ACTIONS: getDisplayNoneRules('.actions'), 149 | DIALOG_CLIMATE_CONTROL_SELECT: getDisplayNoneRules( 150 | ELEMENT.HA_DIALOG_CLIMATE_CONTROL_SELECT 151 | ), 152 | DIALOG_CLIMATE_TEMPERATURE_BUTTONS: getDisplayNoneRules( 153 | ELEMENT.HA_DIALOG_CLIMATE_TEMPERATURE_BUTTONS 154 | ), 155 | DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION: getDisplayNoneRules( 156 | ELEMENT.HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION, 157 | ELEMENT.HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION_SLIDER, 158 | ELEMENT.HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION_TARGET_BORDER, 159 | ELEMENT.HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION_TARGET 160 | ), 161 | DIALOG_LIGHT_CONTROL_ACTIONS: getDisplayNoneRules( 162 | `.controls > ${ELEMENT.HA_DIALOG_LIGHT_BRIGHTNESS} + ${ELEMENT.HA_DIALOG_LIGHT_CONTROLS}` 163 | ), 164 | DIALOG_LIGHT_COLOR_ACTIONS: getDisplayNoneRules( 165 | `.controls > ${ELEMENT.HA_DIALOG_LIGHT_COLORS}` 166 | ), 167 | DIALOG_LIGHT_SETTINGS_ACTIONS: getDisplayNoneRules( 168 | `.controls:has(> ${ELEMENT.HA_DIALOG_LIGHT_BRIGHTNESS}) + div > ${ELEMENT.HA_DIALOG_LIGHT_SETTINGS}` 169 | ), 170 | DIALOG_SHOW_MORE: getDisplayNoneRules('.header a') 171 | }; -------------------------------------------------------------------------------- /.hass/config/ui-lovelace.yaml: -------------------------------------------------------------------------------- 1 | kiosk_mode: 2 | debug: true 3 | debug_template: "[[[ is_state('input_boolean.kiosk', 'on') ? 'yes' : 'no' ]]]" 4 | kiosk: "{{ is_state('input_boolean.kiosk', 'on') }}" 5 | hide_header: "[[[ is_state('input_boolean.kiosk_hide_header', 'on') ]]]" 6 | hide_sidebar: "{{ is_state('input_boolean.kiosk_hide_sidebar', 'on') }}" 7 | hide_menubutton: "[[[ is_state('input_boolean.kiosk_hide_menubutton', 'on') ]]]" 8 | hide_overflow: "{{ is_state('input_boolean.kiosk_hide_overflow', 'on') }}" 9 | hide_notifications: "{{ is_state('input_boolean.kiosk_hide_notifications', 'on') }}" 10 | hide_account: "{{ is_state('input_boolean.kiosk_hide_account', 'on') }}" 11 | hide_add_to_home_assistant: "[[[ is_state('input_boolean.kiosk_hide_add_to_home_assistant', 'on') ]]]" 12 | hide_search: "[[[ is_state('input_boolean.kiosk_hide_search', 'on') ]]]" 13 | hide_assistant: "{{ is_state('input_boolean.kiosk_hide_assistant', 'on') }}" 14 | hide_edit_dashboard: "{{ is_state('input_boolean.kiosk_hide_edit_dashboard', 'on') }}" 15 | hide_refresh: "{{ is_state('input_boolean.kiosk_hide_refresh', 'on') }}" 16 | hide_unused_entities: "[[[ is_state('input_boolean.kiosk_hide_unused_entities', 'on') ]]]" 17 | hide_reload_resources: "{{ is_state('input_boolean.kiosk_hide_reload_resources', 'on') }}" 18 | hide_dialog_header_breadcrumb_navigation: "{{ is_state('input_boolean.kiosk_hide_dialog_header_breadcrumb_navigation', 'on') }}" 19 | hide_dialog_header_history: "{{ is_state('input_boolean.kiosk_hide_dialog_header_history', 'on') }}" 20 | hide_dialog_header_settings: "{{ is_state('input_boolean.kiosk_hide_dialog_header_settings', 'on') }}" 21 | hide_dialog_header_action_items: "{{ is_state('input_boolean.kiosk_hide_dialog_header_action_items', 'on') }}" 22 | hide_dialog_header_overflow: "{{ is_state('input_boolean.kiosk_hide_dialog_header_overflow', 'on') }}" 23 | hide_dialog_history: "{{ is_state('input_boolean.kiosk_hide_dialog_history', 'on') }}" 24 | hide_dialog_logbook: "{{ is_state('input_boolean.kiosk_hide_dialog_logbook', 'on') }}" 25 | hide_dialog_attributes: "{{ is_state('input_boolean.kiosk_hide_dialog_attributes', 'on') }}" 26 | hide_dialog_update_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_update_actions', 'on') }}" 27 | hide_dialog_camera_actions: "[[[ is_state('input_boolean.kiosk_hide_dialog_camera_actions', 'on') ]]]" 28 | hide_dialog_media_actions: "[[[ is_state('input_boolean.kiosk_hide_dialog_media_actions', 'on') ]]]" 29 | hide_dialog_timer_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_timer_actions', 'on') }}" 30 | hide_dialog_climate_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_climate_actions', 'on') }}" 31 | hide_dialog_climate_temperature_actions: "[[[ is_state('input_boolean.kiosk_hide_dialog_climate_temperature_actions', 'on') ]]]" 32 | hide_dialog_climate_settings_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_climate_settings_actions', 'on') }}" 33 | hide_dialog_light_actions: "[[[ is_state('input_boolean.kiosk_hide_dialog_light_actions', 'on') ]]]" 34 | hide_dialog_light_control_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_light_control_actions', 'on') }}" 35 | hide_dialog_light_color_actions: "[[[ is_state('input_boolean.kiosk_hide_dialog_light_color_actions', 'on') ]]]" 36 | hide_dialog_light_settings_actions: "{{ is_state('input_boolean.kiosk_hide_dialog_light_settings_actions', 'on') }}" 37 | hide_dialog_history_show_more: "{{ is_state('input_boolean.kiosk_hide_dialog_history_show_more', 'on') }}" 38 | hide_dialog_logbook_show_more: "{{ is_state('input_boolean.kiosk_hide_dialog_logbook_show_more', 'on') }}" 39 | block_overflow: "{{ is_state('input_boolean.kiosk_block_overflow', 'on') }}" 40 | block_mouse: "{{ is_state('input_boolean.kiosk_block_mouse', 'on') }}" 41 | block_context_menu: "{{ is_state('input_boolean.kiosk_block_context_menu', 'on') }}" 42 | title: Kiosk-mode Overview 43 | views: 44 | - theme: Backend-selected 45 | title: Kiosk-mode overview 46 | path: kiosk-mode-overview 47 | badges: [] 48 | cards: 49 | - type: entities 50 | title: Kiosk settings 51 | entities: 52 | - type: section 53 | label: General options 54 | - entity: input_boolean.kiosk 55 | - entity: input_boolean.kiosk_hide_header 56 | - entity: input_boolean.kiosk_hide_sidebar 57 | - entity: input_boolean.kiosk_hide_menubutton 58 | - entity: input_boolean.kiosk_hide_notifications 59 | - entity: input_boolean.kiosk_hide_account 60 | - entity: input_boolean.kiosk_hide_add_to_home_assistant 61 | - entity: input_boolean.kiosk_hide_search 62 | - entity: input_boolean.kiosk_hide_assistant 63 | - entity: input_boolean.kiosk_hide_overflow 64 | - entity: input_boolean.kiosk_hide_refresh 65 | - entity: input_boolean.kiosk_hide_unused_entities 66 | - entity: input_boolean.kiosk_hide_reload_resources 67 | - entity: input_boolean.kiosk_hide_edit_dashboard 68 | - entity: input_boolean.kiosk_block_overflow 69 | - entity: input_boolean.kiosk_block_mouse 70 | - entity: input_boolean.kiosk_block_context_menu 71 | - type: section 72 | label: More-info dialogs options 73 | - entity: input_boolean.kiosk_hide_dialog_header_breadcrumb_navigation 74 | - entity: input_boolean.kiosk_hide_dialog_header_history 75 | - entity: input_boolean.kiosk_hide_dialog_header_settings 76 | - entity: input_boolean.kiosk_hide_dialog_header_overflow 77 | - entity: input_boolean.kiosk_hide_dialog_header_action_items 78 | - entity: input_boolean.kiosk_hide_dialog_history 79 | - entity: input_boolean.kiosk_hide_dialog_logbook 80 | - entity: input_boolean.kiosk_hide_dialog_attributes 81 | - entity: input_boolean.kiosk_hide_dialog_update_actions 82 | - entity: input_boolean.kiosk_hide_dialog_camera_actions 83 | - entity: input_boolean.kiosk_hide_dialog_media_actions 84 | - entity: input_boolean.kiosk_hide_dialog_climate_actions 85 | - entity: input_boolean.kiosk_hide_dialog_climate_temperature_actions 86 | - entity: input_boolean.kiosk_hide_dialog_climate_settings_actions 87 | - entity: input_boolean.kiosk_hide_dialog_light_actions 88 | - entity: input_boolean.kiosk_hide_dialog_light_control_actions 89 | - entity: input_boolean.kiosk_hide_dialog_light_color_actions 90 | - entity: input_boolean.kiosk_hide_dialog_light_settings_actions 91 | - entity: input_boolean.kiosk_hide_dialog_timer_actions 92 | - entity: input_boolean.kiosk_hide_dialog_history_show_more 93 | - entity: input_boolean.kiosk_hide_dialog_logbook_show_more 94 | - type: section 95 | label: Entities 96 | - entity: sun.sun 97 | - entity: zone.home 98 | - entity: timer.laundry 99 | - entity: climate.ecobee 100 | - entity: media_player.bedroom 101 | - entity: camera.demo_camera 102 | - entity: light.bed_light 103 | - entity: update.demo_add_on 104 | - entity: binary_sensor.basement_floor_wet 105 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const NAMESPACE = 'kiosk-mode'; 2 | export const STYLES_PREFIX = 'kiosk_mode'; 3 | export const NON_CRITICAL_WARNING = '[ Non critial warning ]'; 4 | 5 | export enum OPTION { 6 | KIOSK = 'kiosk', 7 | HIDE_SIDEBAR = 'hide_sidebar', 8 | HIDE_HEADER = 'hide_header', 9 | HIDE_ADD_TO_HOME_ASSISTANT = 'hide_add_to_home_assistant', 10 | HIDE_OVERFLOW = 'hide_overflow', 11 | HIDE_MENU_BUTTON = 'hide_menubutton', 12 | HIDE_ACCOUNT = 'hide_account', 13 | HIDE_NOTIFICATIONS = 'hide_notifications', 14 | HIDE_SEARCH = 'hide_search', 15 | HIDE_ASSISTANT = 'hide_assistant', 16 | HIDE_REFRESH = 'hide_refresh', 17 | HIDE_UNUSED_ENTITIES = 'hide_unused_entities', 18 | HIDE_RELOAD_RESOURCES = 'hide_reload_resources', 19 | HIDE_EDIT_DASHBOARD = 'hide_edit_dashboard', 20 | HIDE_DIALOG_HEADER_BREADCRUMB_NAVIGATION = 'hide_dialog_header_breadcrumb_navigation', 21 | HIDE_DIALOG_HEADER_ACTION_ITEMS = 'hide_dialog_header_action_items', 22 | HIDE_DIALOG_HEADER_HISTORY = 'hide_dialog_header_history', 23 | HIDE_DIALOG_HEADER_SETTINGS = 'hide_dialog_header_settings', 24 | HIDE_DIALOG_HEADER_OVERFLOW = 'hide_dialog_header_overflow', 25 | HIDE_DIALOG_HISTORY = 'hide_dialog_history', 26 | HIDE_DIALOG_LOGBOOK = 'hide_dialog_logbook', 27 | HIDE_DIALOG_ATTRIBUTES = 'hide_dialog_attributes', 28 | HIDE_DIALOG_MEDIA_ACTIONS = 'hide_dialog_media_actions', 29 | HIDE_DIALOG_UPDATE_ACTIONS = 'hide_dialog_update_actions', 30 | HIDE_DIALOG_CAMERA_ACTIONS = 'hide_dialog_camera_actions', 31 | HIDE_DIALOG_CLIMATE_ACTIONS = 'hide_dialog_climate_actions', 32 | HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS = 'hide_dialog_climate_temperature_actions', 33 | HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS = 'hide_dialog_climate_settings_actions', 34 | HIDE_DIALOG_TIMER_ACTIONS = 'hide_dialog_timer_actions', 35 | HIDE_DIALOG_LIGHT_ACTIONS = 'hide_dialog_light_actions', 36 | HIDE_DIALOG_LIGHT_CONTROL_ACTIONS = 'hide_dialog_light_control_actions', 37 | HIDE_DIALOG_LIGHT_COLOR_ACTIONS = 'hide_dialog_light_color_actions', 38 | HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS = 'hide_dialog_light_settings_actions', 39 | HIDE_DIALOG_HISTORY_SHOW_MORE = 'hide_dialog_history_show_more', 40 | HIDE_DIALOG_LOGBOOK_SHOW_MORE = 'hide_dialog_logbook_show_more', 41 | BLOCK_OVERFLOW = 'block_overflow', 42 | BLOCK_MOUSE = 'block_mouse', 43 | BLOCK_CONTEXT_MENU = 'block_context_menu', 44 | } 45 | 46 | export enum CONDITIONAL_OPTION { 47 | IGNORE_MOBILE_SETTINGS = 'ignore_mobile_settings', 48 | IGNORE_DISABLE_KM = 'ignore_disable_km' 49 | } 50 | 51 | export enum DEBUG_CONFIG_OPTION { 52 | DEBUG = 'debug', 53 | DEBUG_TEMPLATE = 'debug_template' 54 | } 55 | 56 | export enum SPECIAL_QUERY_PARAMS { 57 | CACHE = 'cache', 58 | CLEAR_CACHE = 'clear_km_cache', 59 | DISABLE_KIOSK_MODE = 'disable_km', 60 | } 61 | 62 | const UI_PREFIX = 'ui'; 63 | const COMMON_PREFIX = `${UI_PREFIX}.common`; 64 | const PANEL_PREFIX = `${UI_PREFIX}.panel`; 65 | const LOVELACE_PREFIX = `${PANEL_PREFIX}.lovelace`; 66 | const MENU_PREFIX = `${LOVELACE_PREFIX}.menu`; 67 | const EDITOR_PREFIX = `${LOVELACE_PREFIX}.editor`; 68 | const MENU_EDITOR_PREFIX = `${EDITOR_PREFIX}.menu`; 69 | const DIALOGS_PREFIX = `${UI_PREFIX}.dialogs.more_info_control`; 70 | 71 | export enum MENU { 72 | ADD = 'ADD', 73 | OVERFLOW = 'OVERFLOW', 74 | SEARCH = 'SEARCH', 75 | ASSIST = 'ASSIST', 76 | REFRESH = 'REFRESH', 77 | UNUSED_ENTITIES = 'UNUSED_ENTITIES', 78 | RELOAD_RESOURCES = 'RELOAD_RESOURCES', 79 | EDIT_DASHBOARD = 'EDIT_DASHBOARD', 80 | DIALOG_DISMISS = 'DIALOG_DISMISS', 81 | DIALOG_HISTORY = 'DIALOG_HISTORY', 82 | DIALOG_SETTINGS = 'DIALOG_SETTINGS' 83 | } 84 | 85 | export const MENU_REFERENCES = Object.freeze({ 86 | [MENU.ADD]: `${MENU_PREFIX}.add`, 87 | [MENU.OVERFLOW]: `${MENU_EDITOR_PREFIX}.open`, 88 | [MENU.SEARCH]: `${MENU_PREFIX}.search_entities`, 89 | [MENU.ASSIST]: `${MENU_PREFIX}.assist_tooltip`, 90 | [MENU.REFRESH]: `${COMMON_PREFIX}.refresh`, 91 | [MENU.UNUSED_ENTITIES]: `${LOVELACE_PREFIX}.unused_entities.title`, 92 | [MENU.RELOAD_RESOURCES]: `${MENU_PREFIX}.reload_resources`, 93 | [MENU.EDIT_DASHBOARD]: `${MENU_PREFIX}.configure_ui`, 94 | [MENU.DIALOG_HISTORY]: `${DIALOGS_PREFIX}.history`, 95 | [MENU.DIALOG_SETTINGS]: `${DIALOGS_PREFIX}.settings`, 96 | [MENU.DIALOG_DISMISS]: `${COMMON_PREFIX}.close` 97 | }); 98 | 99 | export enum ELEMENT { 100 | HOME_ASSISTANT = 'home-assistant', 101 | HA_PANEL_LOVELACE = 'ha-panel-lovelace', 102 | HUI_VIEW = 'hui-view', 103 | MENU_ITEM = 'ha-icon-button', 104 | MENU_ITEM_ICON = 'mwc-icon-button', 105 | BUTTON_MENU = 'ha-button-menu', 106 | OVERLAY_MENU_ITEM = 'ha-list-item', 107 | TOOLBAR = '.toolbar', 108 | ACTION_ITEMS = '.action-items', 109 | HA_MORE_INFO_DIALOG = 'ha-more-info-dialog', 110 | HA_DIALOG = 'ha-dialog', 111 | HA_DIALOG_HEADER = 'ha-dialog-header', 112 | HA_DIALOG_MORE_INFO = 'ha-more-info-info', 113 | HA_DIALOG_HISTORY = 'ha-more-info-history', 114 | HA_DIALOG_LOGBOOK = 'ha-more-info-logbook', 115 | HA_DIALOG_MORE_INFO_CONTENT = 'more-info-content', 116 | HA_DIALOG_MORE_INFO_HISTORY_AND_LOGBOOK = 'ha-more-info-history-and-logbook', 117 | HA_DIALOG_DEFAULT = 'more-info-default', 118 | HA_DIALOG_TIMER = 'more-info-timer', 119 | HA_DIALOG_VACUUM = 'more-info-vacuum', 120 | HA_DIALOG_CAMERA = 'more-info-camera', 121 | HA_DIALOG_SIREN = 'more-info-siren', 122 | HA_DIALOG_PERSON = 'more-info-person', 123 | HA_DIALOG_MEDIA_PLAYER = 'more-info-media_player', 124 | HA_DIALOG_LIGHT = 'more-info-light', 125 | HA_DIALOG_UPDATE = 'more-info-update', 126 | HA_DIALOG_LOCK = 'more-info-lock', 127 | HA_DIALOG_CLIMATE = 'more-info-climate', 128 | HA_DIALOG_CLIMATE_CONTROL_SELECT = 'ha-more-info-control-select-container', 129 | HA_STATE_CONTROL_CLIMATE_TEMPERATURE = 'ha-state-control-climate-temperature', 130 | HA_DIALOG_CLIMATE_TEMPERATURE_BUTTONS = '.buttons', 131 | HA_DIALOG_CLIMATE_CIRCULAR_SLIDER = 'ha-control-circular-slider', 132 | HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION = '#interaction', 133 | HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION_SLIDER = 'path[role="slider"]', 134 | HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION_TARGET = '.target', 135 | HA_DIALOG_CLIMATE_CIRCULAR_SLIDER_INTERACTION_TARGET_BORDER = '.target-border', 136 | HA_DIALOG_LIGHT_BRIGHTNESS = 'ha-state-control-light-brightness', 137 | HA_DIALOG_LIGHT_CONTROLS = 'ha-icon-button-group', 138 | HA_DIALOG_LIGHT_COLORS = 'ha-more-info-light-favorite-colors', 139 | HA_DIALOG_LIGHT_SETTINGS = 'ha-more-info-control-select-container', 140 | HA_DIALOG_ATTRIBUTES = 'ha-attributes' 141 | } 142 | 143 | export const RESOURCE_WITH_SUFFIX_REGEXP = /^(.*?)( \([A-Z]\))?$/; 144 | 145 | export const TRUE = 'true'; 146 | export const JS_TEMPLATE_REG = /^\s*\[\[\[([\s\S]+)\]\]\]\s*$/; 147 | export const JINJA_TEMPLATE_REG = /\{\{[\s\S]*\}\}|\{%[\s\S]*%\}/; 148 | export const CUSTOM_MOBILE_WIDTH_DEFAULT = 812; 149 | export const TOGGLE_MENU_EVENT = 'hass-toggle-menu'; 150 | export const MC_DRAWER_CLOSED_EVENT = 'MDCDrawer:closed'; 151 | export const CONTEXT_MENU_EVENT = 'contextmenu'; 152 | export const RENDER_TEMPLATE_EVENT = 'render_template'; 153 | export const RESIZE_EVENT = 'resize'; 154 | export const MAX_ATTEMPTS = 500; 155 | export const RETRY_DELAY = 50; 156 | export const WINDOW_RESIZE_DELAY = 250; -------------------------------------------------------------------------------- /tests/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const BASE_URL = 'http://host.docker.internal:8123'; 2 | 3 | export const SELECTORS = { 4 | HUI_MASONRY_VIEW: 'hui-masonry-view', 5 | HA_SIDEBAR: 'ha-sidebar', 6 | HEADER: '.header', 7 | MENU_BUTTON: '.menu > ha-icon-button', 8 | NOTIFICATIONS: 'ha-md-list-item.notifications', 9 | ACCOUNT: 'ha-md-list-item.user', 10 | ADD_TO_HOME_ASSISTANT: 'ha-button-menu[slot="actionItems"]:first-of-type', 11 | SEARCH_BUTTON: 'ha-icon-button[data-selector="SEARCH"]', 12 | ASSISTANT_BUTTON: 'ha-icon-button[data-selector="ASSIST"]', 13 | OVERFLOW_BUTTON: 'ha-button-menu[slot="actionItems"]:last-of-type', 14 | MENU_REFRESH_ITEM: 'ha-list-item[data-selector="REFRESH"]', 15 | MENU_UNUSED_ENTITIES_ITEM: 'ha-list-item[data-selector="UNUSED_ENTITIES"]', 16 | MENU_RELOAD_RESOURCES_ITEM: 'ha-list-item[data-selector="RELOAD_RESOURCES"]', 17 | MENU_EDIT_DASHBOARD_ITEM: 'ha-list-item[data-selector="EDIT_DASHBOARD"]', 18 | ENTITY_ROW: 'hui-simple-entity-row', 19 | TOGGLE_ENTITY_ROW: 'hui-toggle-entity-row', 20 | UPDATE_ENTITY_ROW: 'hui-update-entity-row', 21 | MEDIA_PLAYER_ENTITY_ROW: 'hui-media-player-entity-row state-badge', 22 | CLIMATE_ENTITY_ROW: 'hui-climate-entity-row', 23 | TIMER_ENTITY_ROW: 'hui-timer-entity-row' 24 | }; 25 | 26 | export const DIALOGS_SELECTORS = { 27 | MORE_INFO_INFO: 'ha-more-info-info', 28 | HISTORY_BUTTON: 'ha-icon-button[data-selector="DIALOG_HISTORY"]', 29 | SETTINGS_BUTTON: 'ha-icon-button[data-selector="DIALOG_SETTINGS"]', 30 | OVERFLOW_BUTTON: 'ha-icon-button[data-selector="DIALOG_SETTINGS"] + ha-button-menu', 31 | CLOSE_BUTTON: 'ha-icon-button[data-selector="DIALOG_DISMISS"]', 32 | BREADCRUMB_NAVIGATION: 'ha-dialog-header > .title > .breadcrumb', 33 | HISTORY: '.content > ha-more-info-history', 34 | HISTORY_LINK: 'ha-more-info-history .header > a', 35 | LOGBOOK: '.content > ha-more-info-logbook', 36 | LOGBOOK_LINK: 'ha-more-info-logbook .header > a', 37 | ATTRIBUTES: 'more-info-default ha-attributes', 38 | ACTIONS: '.actions', 39 | UPDATE_ACTIONS: 'more-info-update .actions', 40 | MEDIA_ACTIONS_MAIN: '.bottom-controls > .main-controls', 41 | MEDIA_ACTIONS_SECONDARY: '.bottom-controls > .controls-row', 42 | CAMERA_ACTIONS: 'more-info-camera .actions', 43 | CLIMATE_TEMPERATURE_BUTTONS: 'more-info-climate ha-state-control-climate-temperature .container .buttons', 44 | CLIMATE_SETTINGS_BUTTONS: 'more-info-climate > ha-more-info-control-select-container', 45 | LIGHT_CONTROL_ACTIONS: 'more-info-light .controls > ha-icon-button-group', 46 | LIGHT_COLOR_ACTIONS: 'more-info-light ha-more-info-light-favorite-colors', 47 | LIGHT_SETTINGS_ACTIONS: 'more-info-light ha-more-info-control-select-container', 48 | TIMER_ACTIONS: 'more-info-timer .actions' 49 | }; 50 | 51 | export const ENTITIES = { 52 | KIOSK: 'kiosk', 53 | HIDE_HEADER: 'kiosk_hide_header', 54 | HIDE_SIDEBAR: 'kiosk_hide_sidebar', 55 | HIDE_MENU_BUTTON: 'kiosk_hide_menubutton', 56 | HIDE_NOTIFICATIONS: 'kiosk_hide_notifications', 57 | HIDE_ACCOUNT: 'kiosk_hide_account', 58 | HIDE_ADD_TO_HOME_ASSISTANT: 'kiosk_hide_add_to_home_assistant', 59 | HIDE_SEARCH: 'kiosk_hide_search', 60 | HIDE_ASSISTANT: 'kiosk_hide_assistant', 61 | HIDE_OVERFLOW: 'kiosk_hide_overflow', 62 | HIDE_REFRESH: 'kiosk_hide_refresh', 63 | HIDE_UNUSED_ENTITIES: 'kiosk_hide_unused_entities', 64 | HIDE_RELOAD_RESOURCES: 'kiosk_hide_reload_resources', 65 | HIDE_EDIT_DASHBOARD: 'kiosk_hide_edit_dashboard', 66 | BLOCK_OVERFLOW: 'kiosk_block_overflow', 67 | BLOCK_CONTEXT_MENU: 'kiosk_block_context_menu', 68 | HIDE_DIALOG_HEADER_BREADCRUMB_NAVIGATION: 'kiosk_hide_dialog_header_breadcrumb_navigation', 69 | HIDE_DIALOG_HISTORY_BUTTON: 'kiosk_hide_dialog_header_history', 70 | HIDE_DIALOG_SETTINGS_BUTTON: 'kiosk_hide_dialog_header_settings', 71 | HIDE_DIALOG_OVERFLOW_BUTTON: 'kiosk_hide_dialog_header_overflow', 72 | HIDE_DIALOG_ACTION_ITEMS: 'kiosk_hide_dialog_header_action_items', 73 | HIDE_DIALOG_HISTORY: 'kiosk_hide_dialog_history', 74 | HIDE_DIALOG_LOGBOOK: 'kiosk_hide_dialog_logbook', 75 | HIDE_DIALOG_ATTRIBUTES: 'kiosk_hide_dialog_attributes', 76 | HIDE_DIALOG_UPDATE_ACTIONS: 'kiosk_hide_dialog_update_actions', 77 | HIDE_DIALOG_CAMERA_ACTIONS: 'kiosk_hide_dialog_camera_actions', 78 | HIDE_DIALOG_MEDIA_ACTIONS: 'kiosk_hide_dialog_media_actions', 79 | HIDE_DIALOG_CLIMATE_ACTIONS: 'kiosk_hide_dialog_climate_actions', 80 | HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS: 'kiosk_hide_dialog_climate_temperature_actions', 81 | HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS: 'kiosk_hide_dialog_climate_settings_actions', 82 | HIDE_DIALOG_LIGHT_ACTIONS: 'kiosk_hide_dialog_light_actions', 83 | HIDE_DIALOG_LIGHT_CONTROL_ACTIONS: 'kiosk_hide_dialog_light_control_actions', 84 | HIDE_DIALOG_LIGHT_COLOR_ACTIONS: 'kiosk_hide_dialog_light_color_actions', 85 | HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS: 'kiosk_hide_dialog_light_settings_actions', 86 | HIDE_DIALOG_TIMER_ACTIONS: 'kiosk_hide_dialog_timer_actions', 87 | HIDE_DIALOG_HISTORY_SHOW_MORE: 'kiosk_hide_dialog_history_show_more', 88 | HIDE_DIALOG_LOGBOOK_SHOW_MORE: 'kiosk_hide_dialog_logbook_show_more' 89 | }; 90 | 91 | export const URL_PARAMS = { 92 | KIOSK: 'kiosk', 93 | HIDE_HEADER: 'hide_header', 94 | HIDE_SIDEBAR: 'hide_sidebar', 95 | HIDE_MENU_BUTTON: 'hide_menubutton', 96 | HIDE_NOTIFICATIONS: 'hide_notifications', 97 | HIDE_ACCOUNT: 'hide_account', 98 | HIDE_ADD_TO_HOME_ASSISTANT: 'hide_add_to_home_assistant', 99 | HIDE_SEARCH: 'hide_search', 100 | HIDE_ASSISTANT: 'hide_assistant', 101 | HIDE_OVERFLOW: 'hide_overflow', 102 | HIDE_REFRESH: 'hide_refresh', 103 | HIDE_UNUSED_ENTITIES: 'hide_unused_entities', 104 | HIDE_RELOAD_RESOURCES: 'hide_reload_resources', 105 | HIDE_EDIT_DASHBOARD: 'hide_edit_dashboard', 106 | BLOCK_OVERFLOW: 'block_overflow', 107 | BLOCK_CONTEXT_MENU: 'block_context_menu', 108 | DISABLE_KM: 'disable_km', 109 | CACHE: 'cache', 110 | CLEAR_KM_CACHE: 'clear_km_cache', 111 | HIDE_DIALOG_HEADER_BREADCRUMB_NAVIGATION: 'hide_dialog_header_breadcrumb_navigation', 112 | HIDE_DIALOG_HEADER_HISTORY: 'hide_dialog_header_history', 113 | HIDE_DIALOG_HEADER_SETTINGS: 'hide_dialog_header_settings', 114 | HIDE_DIALOG_HEADER_OVERFLOW: 'hide_dialog_header_overflow', 115 | HIDE_DIALOG_HEADER_ACTION_ITEMS: 'hide_dialog_header_action_items', 116 | HIDE_DIALOG_HISTORY: 'hide_dialog_history', 117 | HIDE_DIALOG_LOGBOOK: 'hide_dialog_logbook', 118 | HIDE_DIALOG_ATTRIBUTES: 'hide_dialog_attributes', 119 | HIDE_DIALOG_UPDATE_ACTIONS: 'hide_dialog_update_actions', 120 | HIDE_DIALOG_CAMERA_ACTIONS: 'hide_dialog_camera_actions', 121 | HIDE_DIALOG_MEDIA_ACTIONS: 'hide_dialog_media_actions', 122 | HIDE_DIALOG_CLIMATE_ACTIONS: 'hide_dialog_climate_actions', 123 | HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS: 'hide_dialog_climate_temperature_actions', 124 | HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS: 'hide_dialog_climate_settings_actions', 125 | HIDE_DIALOG_LIGHT_ACTIONS: 'hide_dialog_light_actions', 126 | HIDE_DIALOG_LIGHT_CONTROL_ACTIONS: 'hide_dialog_light_control_actions', 127 | HIDE_DIALOG_LIGHT_COLOR_ACTIONS: 'hide_dialog_light_color_actions', 128 | HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS: 'hide_dialog_light_settings_actions', 129 | HIDE_DIALOG_TIMER_ACTIONS: 'hide_dialog_timer_actions', 130 | HIDE_DIALOG_HISTORY_SHOW_MORE: 'hide_dialog_history_show_more', 131 | HIDE_DIALOG_LOGBOOK_SHOW_MORE: 'hide_dialog_logbook_show_more' 132 | }; 133 | 134 | export const TEXT_SELECTORS = { 135 | HOME: { hasText: 'Home' }, 136 | BINARY_SENSOR: { hasText: 'Basement Floor Wet' }, 137 | ADDON: { hasText: 'Demo add-on' }, 138 | CAMERA: { hasText: 'Demo camera' }, 139 | CAMERA_ACTION: { hasText: 'Download snapshot' }, 140 | CLIMATE: { hasText: 'Ecobee' }, 141 | LIGHT: { hasText: 'Bed Light' }, 142 | TIMER: { hasText: 'laundry' }, 143 | UPDATE_ACTION: { hasText: 'Update' } 144 | }; -------------------------------------------------------------------------------- /.hass/config/.storage/homeassistant.exposed_entities: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "minor_version": 1, 4 | "key": "homeassistant.exposed_entities", 5 | "data": { 6 | "assistants": {}, 7 | "exposed_entities": { 8 | "sun.sun": { 9 | "assistants": { 10 | "conversation": { 11 | "should_expose": false 12 | } 13 | } 14 | }, 15 | "zone.home": { 16 | "assistants": { 17 | "conversation": { 18 | "should_expose": false 19 | } 20 | } 21 | }, 22 | "conversation.home_assistant": { 23 | "assistants": { 24 | "conversation": { 25 | "should_expose": false 26 | } 27 | } 28 | }, 29 | "device_tracker.demo_paulus": { 30 | "assistants": { 31 | "conversation": { 32 | "should_expose": false 33 | } 34 | } 35 | }, 36 | "device_tracker.demo_anne_therese": { 37 | "assistants": { 38 | "conversation": { 39 | "should_expose": false 40 | } 41 | } 42 | }, 43 | "device_tracker.demo_home_boy": { 44 | "assistants": { 45 | "conversation": { 46 | "should_expose": false 47 | } 48 | } 49 | }, 50 | "image_processing.demo_face": { 51 | "assistants": { 52 | "conversation": { 53 | "should_expose": false 54 | } 55 | } 56 | }, 57 | "air_quality.demo_air_quality_home": { 58 | "assistants": { 59 | "conversation": { 60 | "should_expose": false 61 | } 62 | } 63 | }, 64 | "air_quality.demo_air_quality_office": { 65 | "assistants": { 66 | "conversation": { 67 | "should_expose": false 68 | } 69 | } 70 | }, 71 | "camera.demo_camera": { 72 | "assistants": { 73 | "conversation": { 74 | "should_expose": false 75 | } 76 | } 77 | }, 78 | "camera.demo_camera_png": { 79 | "assistants": { 80 | "conversation": { 81 | "should_expose": false 82 | } 83 | } 84 | }, 85 | "camera.demo_camera_without_stream": { 86 | "assistants": { 87 | "conversation": { 88 | "should_expose": false 89 | } 90 | } 91 | }, 92 | "calendar.calendar_1": { 93 | "assistants": { 94 | "conversation": { 95 | "should_expose": false 96 | } 97 | } 98 | }, 99 | "calendar.calendar_2": { 100 | "assistants": { 101 | "conversation": { 102 | "should_expose": false 103 | } 104 | } 105 | }, 106 | "humidifier.humidifier": { 107 | "assistants": { 108 | "conversation": { 109 | "should_expose": true 110 | } 111 | } 112 | }, 113 | "humidifier.dehumidifier": { 114 | "assistants": { 115 | "conversation": { 116 | "should_expose": true 117 | } 118 | } 119 | }, 120 | "humidifier.hygrostat": { 121 | "assistants": { 122 | "conversation": { 123 | "should_expose": true 124 | } 125 | } 126 | }, 127 | "lock.front_door": { 128 | "assistants": { 129 | "conversation": { 130 | "should_expose": false 131 | } 132 | } 133 | }, 134 | "lock.kitchen_door": { 135 | "assistants": { 136 | "conversation": { 137 | "should_expose": false 138 | } 139 | } 140 | }, 141 | "lock.poorly_installed_door": { 142 | "assistants": { 143 | "conversation": { 144 | "should_expose": false 145 | } 146 | } 147 | }, 148 | "lock.openable_lock": { 149 | "assistants": { 150 | "conversation": { 151 | "should_expose": false 152 | } 153 | } 154 | }, 155 | "media_player.living_room": { 156 | "assistants": { 157 | "conversation": { 158 | "should_expose": true 159 | } 160 | } 161 | }, 162 | "media_player.bedroom": { 163 | "assistants": { 164 | "conversation": { 165 | "should_expose": true 166 | } 167 | } 168 | }, 169 | "media_player.walkman": { 170 | "assistants": { 171 | "conversation": { 172 | "should_expose": true 173 | } 174 | } 175 | }, 176 | "media_player.kitchen": { 177 | "assistants": { 178 | "conversation": { 179 | "should_expose": true 180 | } 181 | } 182 | }, 183 | "media_player.lounge_room": { 184 | "assistants": { 185 | "conversation": { 186 | "should_expose": true 187 | } 188 | } 189 | }, 190 | "media_player.browse": { 191 | "assistants": { 192 | "conversation": { 193 | "should_expose": true 194 | } 195 | } 196 | }, 197 | "media_player.group": { 198 | "assistants": { 199 | "conversation": { 200 | "should_expose": true 201 | } 202 | } 203 | }, 204 | "siren.siren": { 205 | "assistants": { 206 | "conversation": { 207 | "should_expose": false 208 | } 209 | } 210 | }, 211 | "siren.siren_with_all_features": { 212 | "assistants": { 213 | "conversation": { 214 | "should_expose": false 215 | } 216 | } 217 | }, 218 | "stt.demo_stt": { 219 | "assistants": { 220 | "conversation": { 221 | "should_expose": false 222 | } 223 | } 224 | }, 225 | "vacuum.0_ground_floor": { 226 | "assistants": { 227 | "conversation": { 228 | "should_expose": true 229 | } 230 | } 231 | }, 232 | "vacuum.1_first_floor": { 233 | "assistants": { 234 | "conversation": { 235 | "should_expose": true 236 | } 237 | } 238 | }, 239 | "vacuum.2_second_floor": { 240 | "assistants": { 241 | "conversation": { 242 | "should_expose": true 243 | } 244 | } 245 | }, 246 | "vacuum.3_third_floor": { 247 | "assistants": { 248 | "conversation": { 249 | "should_expose": true 250 | } 251 | } 252 | }, 253 | "vacuum.4_fourth_floor": { 254 | "assistants": { 255 | "conversation": { 256 | "should_expose": true 257 | } 258 | } 259 | }, 260 | "water_heater.demo_water_heater": { 261 | "assistants": { 262 | "conversation": { 263 | "should_expose": true 264 | } 265 | } 266 | }, 267 | "water_heater.demo_water_heater_celsius": { 268 | "assistants": { 269 | "conversation": { 270 | "should_expose": true 271 | } 272 | } 273 | }, 274 | "media_player.search": { 275 | "assistants": { 276 | "conversation": { 277 | "should_expose": true 278 | } 279 | } 280 | }, 281 | "valve.front_garden": { 282 | "assistants": { 283 | "conversation": { 284 | "should_expose": false 285 | } 286 | } 287 | }, 288 | "valve.orchard": { 289 | "assistants": { 290 | "conversation": { 291 | "should_expose": false 292 | } 293 | } 294 | } 295 | } 296 | } 297 | } -------------------------------------------------------------------------------- /tests/01 - main-options.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'playwright-test-coverage'; 2 | import path from 'path'; 3 | import { SELECTORS, ENTITIES } from './constants'; 4 | import { turnBooleanState, goToPage } from './utils'; 5 | 6 | test('Option: kiosk', async ({ page }) => { 7 | 8 | await goToPage(page); 9 | 10 | await expect(page.locator(SELECTORS.HEADER)).toBeVisible(); 11 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeVisible(); 12 | 13 | await turnBooleanState(page, ENTITIES.KIOSK, true); 14 | 15 | await expect(page.locator(SELECTORS.HEADER)).toBeHidden(); 16 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeHidden(); 17 | await expect(page).toHaveScreenshot('01-kiosk.png'); 18 | 19 | await turnBooleanState(page, ENTITIES.KIOSK, false); 20 | 21 | }); 22 | 23 | test('Option: hide_header', async ({ page }) => { 24 | 25 | await goToPage(page); 26 | 27 | await expect(page.locator(SELECTORS.HEADER)).toBeVisible(); 28 | 29 | await turnBooleanState(page, ENTITIES.HIDE_HEADER, true); 30 | 31 | await expect(page.locator(SELECTORS.HEADER)).toBeHidden(); 32 | await expect(page).toHaveScreenshot('02-hide_header.png'); 33 | 34 | await turnBooleanState(page, ENTITIES.HIDE_HEADER, false); 35 | 36 | }); 37 | 38 | test('Option: hide_sidebar', async ({ page }) => { 39 | 40 | await goToPage(page); 41 | 42 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeVisible(); 43 | 44 | await turnBooleanState(page, ENTITIES.HIDE_SIDEBAR, true); 45 | 46 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeHidden(); 47 | await expect(page).toHaveScreenshot('03-hide_sidebar.png'); 48 | 49 | await turnBooleanState(page, ENTITIES.HIDE_SIDEBAR, false); 50 | 51 | }); 52 | 53 | test('Option: hide_menubutton', async ({ page }) => { 54 | 55 | await goToPage(page); 56 | 57 | await expect(page.locator(SELECTORS.MENU_BUTTON)).toBeVisible(); 58 | 59 | await turnBooleanState(page, ENTITIES.HIDE_MENU_BUTTON, true); 60 | 61 | await expect(page.locator(SELECTORS.MENU_BUTTON)).toBeHidden(); 62 | await expect(page).toHaveScreenshot('04-hide_menubutton.png'); 63 | 64 | await turnBooleanState(page, ENTITIES.HIDE_MENU_BUTTON, false); 65 | 66 | }); 67 | 68 | test('Option: hide_notifications', async ({ page }) => { 69 | 70 | await goToPage(page); 71 | 72 | await expect(page.locator(SELECTORS.NOTIFICATIONS)).toBeVisible(); 73 | 74 | await turnBooleanState(page, ENTITIES.HIDE_NOTIFICATIONS, true); 75 | 76 | await expect(page.locator(SELECTORS.NOTIFICATIONS)).toBeHidden(); 77 | await expect(page).toHaveScreenshot('05-hide_notifications.png'); 78 | 79 | await turnBooleanState(page, ENTITIES.HIDE_NOTIFICATIONS, false); 80 | 81 | }); 82 | 83 | test('Option: hide_account', async ({ page }) => { 84 | 85 | await goToPage(page); 86 | 87 | await expect(page.locator(SELECTORS.ACCOUNT)).toBeVisible(); 88 | 89 | await turnBooleanState(page, ENTITIES.HIDE_ACCOUNT, true); 90 | 91 | await expect(page.locator(SELECTORS.ACCOUNT)).toBeHidden(); 92 | await expect(page).toHaveScreenshot('06-hide_account.png'); 93 | 94 | await turnBooleanState(page, ENTITIES.HIDE_ACCOUNT, false); 95 | 96 | }); 97 | 98 | test('Option: hide_notifications and hide_account at the same time', async ({ page }) => { 99 | 100 | await goToPage(page); 101 | 102 | await expect(page.locator(SELECTORS.ACCOUNT)).toBeVisible(); 103 | 104 | await turnBooleanState(page, ENTITIES.HIDE_NOTIFICATIONS, true); 105 | await turnBooleanState(page, ENTITIES.HIDE_ACCOUNT, true); 106 | 107 | await expect(page).toHaveScreenshot('07-hide_notifications-and-hide_account.png'); 108 | 109 | await turnBooleanState(page, ENTITIES.HIDE_NOTIFICATIONS, false); 110 | await turnBooleanState(page, ENTITIES.HIDE_ACCOUNT, false); 111 | 112 | }); 113 | 114 | test('Option: hide_add_to_home_assistant', async ({ page }) => { 115 | 116 | await goToPage(page); 117 | 118 | await expect(page.locator(SELECTORS.ADD_TO_HOME_ASSISTANT)).toBeVisible(); 119 | 120 | await turnBooleanState(page, ENTITIES.HIDE_ADD_TO_HOME_ASSISTANT, true); 121 | 122 | await expect(page.locator(SELECTORS.ADD_TO_HOME_ASSISTANT)).toBeHidden(); 123 | await expect(page).toHaveScreenshot('08-hide_add_to_home_assistant.png'); 124 | 125 | await turnBooleanState(page, ENTITIES.HIDE_ADD_TO_HOME_ASSISTANT, false); 126 | 127 | }); 128 | 129 | test('Option: hide_search', async ({ page }) => { 130 | 131 | await goToPage(page); 132 | 133 | await expect(page.locator(SELECTORS.SEARCH_BUTTON)).toBeVisible(); 134 | 135 | await turnBooleanState(page, ENTITIES.HIDE_SEARCH, true); 136 | 137 | await expect(page.locator(SELECTORS.SEARCH_BUTTON)).toBeHidden(); 138 | await expect(page).toHaveScreenshot('09-hide_search.png'); 139 | 140 | await turnBooleanState(page, ENTITIES.HIDE_SEARCH, false); 141 | 142 | }); 143 | 144 | test('Option: hide_assistant', async ({ page }) => { 145 | 146 | await goToPage(page); 147 | 148 | await expect(page.locator(SELECTORS.ASSISTANT_BUTTON)).toBeVisible(); 149 | 150 | await turnBooleanState(page, ENTITIES.HIDE_ASSISTANT, true); 151 | 152 | await expect(page.locator(SELECTORS.ASSISTANT_BUTTON)).toBeHidden(); 153 | await expect(page).toHaveScreenshot('10-hide_assistant.png'); 154 | 155 | await turnBooleanState(page, ENTITIES.HIDE_ASSISTANT, false); 156 | 157 | }); 158 | 159 | test('Option: hide_overflow', async ({ page }) => { 160 | 161 | await goToPage(page); 162 | 163 | await expect(page.locator(SELECTORS.OVERFLOW_BUTTON)).toBeVisible(); 164 | 165 | await turnBooleanState(page, ENTITIES.HIDE_OVERFLOW, true); 166 | 167 | await expect(page.locator(SELECTORS.OVERFLOW_BUTTON)).toBeHidden(); 168 | await expect(page).toHaveScreenshot('11-hide_overflow.png'); 169 | 170 | await turnBooleanState(page, ENTITIES.HIDE_OVERFLOW, false); 171 | 172 | }); 173 | 174 | test('Option: hide_refresh', async ({ page }) => { 175 | 176 | await goToPage(page); 177 | 178 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 179 | 180 | await expect(page.locator(SELECTORS.MENU_REFRESH_ITEM)).toBeVisible(); 181 | 182 | await turnBooleanState(page, ENTITIES.HIDE_REFRESH, true); 183 | 184 | await expect(page.locator(SELECTORS.MENU_REFRESH_ITEM)).toBeHidden(); 185 | await expect(page).toHaveScreenshot('12-hide_refresh.png'); 186 | 187 | await turnBooleanState(page, ENTITIES.HIDE_REFRESH, false); 188 | 189 | }); 190 | 191 | test('Option: hide_unused_entities', async ({ page }) => { 192 | 193 | await goToPage(page); 194 | 195 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 196 | 197 | await expect(page.locator(SELECTORS.MENU_UNUSED_ENTITIES_ITEM)).toBeVisible(); 198 | 199 | await turnBooleanState(page, ENTITIES.HIDE_UNUSED_ENTITIES, true); 200 | 201 | await expect(page.locator(SELECTORS.MENU_UNUSED_ENTITIES_ITEM)).toBeHidden(); 202 | await expect(page).toHaveScreenshot('13-hide_unused_entities.png'); 203 | 204 | await turnBooleanState(page, ENTITIES.HIDE_UNUSED_ENTITIES, false); 205 | 206 | }); 207 | 208 | test('Option: hide_reload_resources', async ({ page }) => { 209 | 210 | await goToPage(page); 211 | 212 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 213 | 214 | await expect(page.locator(SELECTORS.MENU_RELOAD_RESOURCES_ITEM)).toBeVisible(); 215 | 216 | await turnBooleanState(page, ENTITIES.HIDE_RELOAD_RESOURCES, true); 217 | 218 | await expect(page.locator(SELECTORS.MENU_RELOAD_RESOURCES_ITEM)).toBeHidden(); 219 | await expect(page).toHaveScreenshot('14-hide_reload_resources.png'); 220 | 221 | await turnBooleanState(page, ENTITIES.HIDE_RELOAD_RESOURCES, false); 222 | 223 | }); 224 | 225 | test('Option: hide_edit_dashboard', async ({ page }) => { 226 | 227 | await goToPage(page); 228 | 229 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 230 | 231 | await expect(page.locator(SELECTORS.MENU_EDIT_DASHBOARD_ITEM)).toBeVisible(); 232 | 233 | await turnBooleanState(page, ENTITIES.HIDE_EDIT_DASHBOARD, true); 234 | 235 | await expect(page.locator(SELECTORS.MENU_EDIT_DASHBOARD_ITEM)).toBeHidden(); 236 | await expect(page).toHaveScreenshot('15-hide_edit_dashboard.png'); 237 | 238 | await turnBooleanState(page, ENTITIES.HIDE_EDIT_DASHBOARD, false); 239 | 240 | }); 241 | 242 | test('Option: block_overflow', async ({ page }) => { 243 | 244 | await goToPage(page); 245 | 246 | await expect(page.locator(SELECTORS.OVERFLOW_BUTTON)).not.toHaveCSS('pointer-events', 'none'); 247 | 248 | await turnBooleanState(page, ENTITIES.BLOCK_OVERFLOW, true); 249 | 250 | await expect(page.locator(SELECTORS.OVERFLOW_BUTTON)).toHaveCSS('pointer-events', 'none'); 251 | 252 | await turnBooleanState(page, ENTITIES.BLOCK_OVERFLOW, false); 253 | 254 | }); 255 | 256 | test.describe('Option: block_context_menu', () => { 257 | 258 | test('Test contextmenu event listener', async ({ context, page }) => { 259 | 260 | const header = page.locator(SELECTORS.HEADER); 261 | const sidebar = page.locator(SELECTORS.HA_SIDEBAR); 262 | 263 | await context.addInitScript({ 264 | path: path.join(__dirname, '..', './node_modules/sinon/pkg/sinon.js'), 265 | }); 266 | 267 | await context.addInitScript(() => { 268 | window['__listener'] = window['sinon'].fake(); 269 | window.addEventListener('contextmenu', window['__listener']); 270 | }); 271 | 272 | await goToPage(page); 273 | 274 | await expect(header).toBeVisible(); 275 | await expect(sidebar).toBeVisible(); 276 | 277 | let executed = await page.evaluate(() => window['__listener'].calledOnce); 278 | 279 | await expect(executed).toBe(false); 280 | 281 | await header.click({ 282 | button: 'right' 283 | }); 284 | 285 | executed = await page.evaluate(() => window['__listener'].calledOnce); 286 | 287 | await expect(executed).toBe(true); 288 | 289 | await turnBooleanState(page, ENTITIES.BLOCK_CONTEXT_MENU, true); 290 | 291 | await header.click({ 292 | button: 'right' 293 | }); 294 | 295 | executed = await page.evaluate(() => window['__listener'].calledTwice); 296 | 297 | await expect(executed).toBe(false); 298 | 299 | await turnBooleanState(page, ENTITIES.BLOCK_CONTEXT_MENU, false); 300 | 301 | await header.click({ 302 | button: 'right' 303 | }); 304 | 305 | executed = await page.evaluate(() => window['__listener'].calledTwice); 306 | 307 | await expect(executed).toBe(true); 308 | 309 | }); 310 | }); 311 | 312 | test('Option: debug and debug_template', async ({ page }) => { 313 | 314 | const messages: string[] = []; 315 | 316 | page.on('console', message => { 317 | if ( 318 | ['startGroup', 'startGroupCollapsed', 'endGroup', 'warning', 'info', 'table'].includes(message.type()) 319 | ) { 320 | messages.push(message.text()); 321 | } 322 | }); 323 | 324 | await goToPage(page); 325 | 326 | await expect(page.locator(SELECTORS.HEADER)).toBeVisible(); 327 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeVisible(); 328 | await page.waitForTimeout(1500); 329 | 330 | expect(messages).toEqual( 331 | expect.arrayContaining([ 332 | 'kiosk-mode raw config for lovelace panel', 333 | 'The template debug has been triggered with the next template:', 334 | '%c[[[ is_state(\'input_boolean.kiosk\', \'on\') ? \'yes\' : \'no\' ]]] color: #666', 335 | 'The evaluation of this template is: %cno font-weight: bold; color: red;', 336 | '%c⚠️ This template doesn\'t return a boolean. It cannot be used as a kiosk-mode option. text-decoration: underline' 337 | ]) 338 | ); 339 | 340 | }); -------------------------------------------------------------------------------- /tests/04 - caching.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'playwright-test-coverage'; 2 | import path from 'path'; 3 | import { 4 | URL_PARAMS, 5 | SELECTORS, 6 | DIALOGS_SELECTORS, 7 | TEXT_SELECTORS 8 | } from './constants'; 9 | import { goToPageWithParams, goToPage } from './utils'; 10 | 11 | [ 12 | { 13 | param: URL_PARAMS.KIOSK, 14 | selector: [ 15 | SELECTORS.HEADER, 16 | SELECTORS.HA_SIDEBAR 17 | ] 18 | }, 19 | { 20 | param: URL_PARAMS.HIDE_HEADER, 21 | selector: SELECTORS.HEADER 22 | 23 | }, 24 | { 25 | param: URL_PARAMS.HIDE_SIDEBAR, 26 | selector: SELECTORS.HA_SIDEBAR 27 | }, 28 | { 29 | param: URL_PARAMS.HIDE_MENU_BUTTON, 30 | selector: SELECTORS.MENU_BUTTON 31 | }, 32 | { 33 | param: URL_PARAMS.HIDE_NOTIFICATIONS, 34 | selector: SELECTORS.NOTIFICATIONS 35 | }, 36 | { 37 | param: URL_PARAMS.HIDE_ACCOUNT, 38 | selector: SELECTORS.ACCOUNT 39 | }, 40 | { 41 | param: URL_PARAMS.HIDE_ADD_TO_HOME_ASSISTANT, 42 | selector: SELECTORS.ADD_TO_HOME_ASSISTANT 43 | }, 44 | { 45 | param: URL_PARAMS.HIDE_SEARCH, 46 | selector: SELECTORS.SEARCH_BUTTON 47 | }, 48 | { 49 | param: URL_PARAMS.HIDE_ASSISTANT, 50 | selector: SELECTORS.ASSISTANT_BUTTON 51 | }, 52 | { 53 | param: URL_PARAMS.HIDE_OVERFLOW, 54 | selector: SELECTORS.OVERFLOW_BUTTON 55 | } 56 | ].forEach(({ param, selector }) => { 57 | 58 | const selectors = Array.isArray(selector) 59 | ? selector 60 | : [selector]; 61 | 62 | test(`Caching URL Parameter: ?${param}`, async ({ page }) => { 63 | 64 | const masonryView = page.locator(SELECTORS.HUI_MASONRY_VIEW); 65 | 66 | await goToPageWithParams(page, param, URL_PARAMS.CACHE); 67 | 68 | await expect(masonryView).toBeVisible(); 69 | 70 | for (const selector of selectors) { 71 | await expect(page.locator(selector)).toBeHidden(); 72 | } 73 | 74 | await goToPage(page); 75 | 76 | await expect(masonryView).toBeVisible(); 77 | 78 | for (const selector of selectors) { 79 | await expect(page.locator(selector)).toBeHidden(); 80 | } 81 | 82 | await goToPageWithParams(page, URL_PARAMS.CLEAR_KM_CACHE); 83 | 84 | await expect(masonryView).toBeVisible(); 85 | 86 | for (const selector of selectors) { 87 | await expect(page.locator(selector)).toBeVisible(); 88 | } 89 | 90 | }); 91 | 92 | }); 93 | 94 | [ 95 | { 96 | param: URL_PARAMS.HIDE_REFRESH, 97 | selector: SELECTORS.MENU_REFRESH_ITEM 98 | }, 99 | { 100 | param: URL_PARAMS.HIDE_UNUSED_ENTITIES, 101 | selector: SELECTORS.MENU_UNUSED_ENTITIES_ITEM 102 | }, 103 | { 104 | param: URL_PARAMS.HIDE_RELOAD_RESOURCES, 105 | selector: SELECTORS.MENU_RELOAD_RESOURCES_ITEM 106 | }, 107 | { 108 | param: URL_PARAMS.HIDE_EDIT_DASHBOARD, 109 | selector: SELECTORS.MENU_EDIT_DASHBOARD_ITEM 110 | } 111 | ].forEach(({ param, selector }) => { 112 | 113 | test(`Caching URL Parameter: ?${param}`, async ({ page }) => { 114 | 115 | const masonryView = page.locator(SELECTORS.HUI_MASONRY_VIEW); 116 | const overflowButton = page.locator(SELECTORS.OVERFLOW_BUTTON); 117 | 118 | await goToPageWithParams(page, param, URL_PARAMS.CACHE); 119 | 120 | await expect(masonryView).toBeVisible(); 121 | 122 | await overflowButton.click(); 123 | 124 | await expect(page.locator(selector)).toBeHidden(); 125 | 126 | await goToPage(page); 127 | 128 | await expect(masonryView).toBeVisible(); 129 | 130 | await overflowButton.click(); 131 | 132 | await expect(page.locator(selector)).toBeHidden(); 133 | 134 | await goToPageWithParams(page, URL_PARAMS.CLEAR_KM_CACHE); 135 | 136 | await expect(masonryView).toBeVisible(); 137 | 138 | await overflowButton.click(); 139 | 140 | await expect(page.locator(selector)).toBeVisible(); 141 | 142 | }); 143 | 144 | }); 145 | 146 | [ 147 | { 148 | param: URL_PARAMS.HIDE_DIALOG_HEADER_BREADCRUMB_NAVIGATION, 149 | selector: DIALOGS_SELECTORS.BREADCRUMB_NAVIGATION, 150 | entitySelector: SELECTORS.ENTITY_ROW, 151 | entitySelectorText: TEXT_SELECTORS.BINARY_SENSOR 152 | }, 153 | { 154 | param: URL_PARAMS.HIDE_DIALOG_HEADER_HISTORY, 155 | selector: DIALOGS_SELECTORS.HISTORY_BUTTON, 156 | entitySelector: SELECTORS.UPDATE_ENTITY_ROW, 157 | entitySelectorText: TEXT_SELECTORS.ADDON 158 | }, 159 | { 160 | param: URL_PARAMS.HIDE_DIALOG_HEADER_SETTINGS, 161 | selector: DIALOGS_SELECTORS.SETTINGS_BUTTON, 162 | entitySelector: SELECTORS.UPDATE_ENTITY_ROW, 163 | entitySelectorText: TEXT_SELECTORS.ADDON 164 | }, 165 | { 166 | param: URL_PARAMS.HIDE_DIALOG_HEADER_OVERFLOW, 167 | selector: DIALOGS_SELECTORS.OVERFLOW_BUTTON, 168 | entitySelector: SELECTORS.UPDATE_ENTITY_ROW, 169 | entitySelectorText: TEXT_SELECTORS.ADDON 170 | }, 171 | { 172 | param: URL_PARAMS.HIDE_DIALOG_HEADER_ACTION_ITEMS, 173 | selector: [ 174 | DIALOGS_SELECTORS.HISTORY_BUTTON, 175 | DIALOGS_SELECTORS.SETTINGS_BUTTON, 176 | DIALOGS_SELECTORS.OVERFLOW_BUTTON 177 | ], 178 | entitySelector: SELECTORS.UPDATE_ENTITY_ROW, 179 | entitySelectorText: TEXT_SELECTORS.ADDON 180 | }, 181 | { 182 | param: URL_PARAMS.HIDE_DIALOG_HISTORY, 183 | selector: DIALOGS_SELECTORS.HISTORY, 184 | entitySelector: SELECTORS.ENTITY_ROW, 185 | entitySelectorText: TEXT_SELECTORS.HOME 186 | }, 187 | { 188 | param: URL_PARAMS.HIDE_DIALOG_LOGBOOK, 189 | selector: DIALOGS_SELECTORS.LOGBOOK, 190 | entitySelector: SELECTORS.ENTITY_ROW, 191 | entitySelectorText: TEXT_SELECTORS.BINARY_SENSOR 192 | }, 193 | { 194 | param: URL_PARAMS.HIDE_DIALOG_ATTRIBUTES, 195 | selector: DIALOGS_SELECTORS.ATTRIBUTES, 196 | entitySelector: SELECTORS.ENTITY_ROW, 197 | entitySelectorText: TEXT_SELECTORS.HOME 198 | }, 199 | { 200 | param: URL_PARAMS.HIDE_DIALOG_UPDATE_ACTIONS, 201 | selector: DIALOGS_SELECTORS.UPDATE_ACTIONS, 202 | entitySelector: SELECTORS.UPDATE_ENTITY_ROW, 203 | entitySelectorText: TEXT_SELECTORS.ADDON 204 | }, 205 | { 206 | param: URL_PARAMS.HIDE_DIALOG_CAMERA_ACTIONS, 207 | selector: DIALOGS_SELECTORS.CAMERA_ACTIONS, 208 | entitySelector: SELECTORS.ENTITY_ROW, 209 | entitySelectorText: TEXT_SELECTORS.CAMERA 210 | }, 211 | { 212 | param: URL_PARAMS.HIDE_DIALOG_MEDIA_ACTIONS, 213 | selector: [ 214 | DIALOGS_SELECTORS.MEDIA_ACTIONS_MAIN, 215 | DIALOGS_SELECTORS.MEDIA_ACTIONS_SECONDARY 216 | ], 217 | entitySelector: SELECTORS.MEDIA_PLAYER_ENTITY_ROW 218 | }, 219 | { 220 | param: URL_PARAMS.HIDE_DIALOG_CLIMATE_ACTIONS, 221 | selector: [ 222 | DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS, 223 | DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS 224 | ], 225 | entitySelector: SELECTORS.CLIMATE_ENTITY_ROW, 226 | entitySelectorText: TEXT_SELECTORS.CLIMATE 227 | }, 228 | { 229 | param: URL_PARAMS.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS, 230 | selector: DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS, 231 | entitySelector: SELECTORS.CLIMATE_ENTITY_ROW, 232 | entitySelectorText: TEXT_SELECTORS.CLIMATE 233 | }, 234 | { 235 | param: URL_PARAMS.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS, 236 | selector: DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS, 237 | entitySelector: SELECTORS.CLIMATE_ENTITY_ROW, 238 | entitySelectorText: TEXT_SELECTORS.CLIMATE 239 | }, 240 | { 241 | param: URL_PARAMS.HIDE_DIALOG_LIGHT_ACTIONS, 242 | selector: [ 243 | DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS, 244 | DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS, 245 | DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS 246 | ], 247 | entitySelector: SELECTORS.TOGGLE_ENTITY_ROW, 248 | entitySelectorText: TEXT_SELECTORS.LIGHT 249 | }, 250 | { 251 | param: URL_PARAMS.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS, 252 | selector: DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS, 253 | entitySelector: SELECTORS.TOGGLE_ENTITY_ROW, 254 | entitySelectorText: TEXT_SELECTORS.LIGHT 255 | }, 256 | { 257 | param: URL_PARAMS.HIDE_DIALOG_LIGHT_COLOR_ACTIONS, 258 | selector: DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS, 259 | entitySelector: SELECTORS.TOGGLE_ENTITY_ROW, 260 | entitySelectorText: TEXT_SELECTORS.LIGHT 261 | }, 262 | { 263 | param: URL_PARAMS.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS, 264 | selector: DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS, 265 | entitySelector: SELECTORS.TOGGLE_ENTITY_ROW, 266 | entitySelectorText: TEXT_SELECTORS.LIGHT 267 | }, 268 | { 269 | param: URL_PARAMS.HIDE_DIALOG_TIMER_ACTIONS, 270 | selector: DIALOGS_SELECTORS.TIMER_ACTIONS, 271 | entitySelector: SELECTORS.TIMER_ENTITY_ROW, 272 | entitySelectorText: TEXT_SELECTORS.TIMER 273 | }, 274 | { 275 | param: URL_PARAMS.HIDE_DIALOG_HISTORY_SHOW_MORE, 276 | selector: DIALOGS_SELECTORS.HISTORY_LINK, 277 | entitySelector: SELECTORS.ENTITY_ROW, 278 | entitySelectorText: TEXT_SELECTORS.HOME 279 | }, 280 | { 281 | param: URL_PARAMS.HIDE_DIALOG_LOGBOOK_SHOW_MORE, 282 | selector: DIALOGS_SELECTORS.LOGBOOK_LINK, 283 | entitySelector: SELECTORS.ENTITY_ROW, 284 | entitySelectorText: TEXT_SELECTORS.BINARY_SENSOR 285 | } 286 | ].forEach(({ param, selector, entitySelector, entitySelectorText }) => { 287 | 288 | const selectors = Array.isArray(selector) 289 | ? selector 290 | : [selector]; 291 | 292 | test(`Caching URL Parameter: ?${param}`, async ({ page }) => { 293 | 294 | const masonryView = page.locator(SELECTORS.HUI_MASONRY_VIEW); 295 | const moreInfoDialog = page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO); 296 | 297 | await goToPageWithParams(page, param, URL_PARAMS.CACHE); 298 | 299 | await expect(masonryView).toBeVisible(); 300 | 301 | if (entitySelectorText) { 302 | await page.locator(entitySelector, entitySelectorText).click(); 303 | } else { 304 | await page.locator(entitySelector).click(); 305 | } 306 | 307 | await expect(moreInfoDialog).toBeVisible(); 308 | 309 | for (const selector of selectors) { 310 | await expect(page.locator(selector)).toBeHidden(); 311 | } 312 | 313 | await goToPage(page); 314 | 315 | await expect(masonryView).toBeVisible(); 316 | 317 | if (entitySelectorText) { 318 | await page.locator(entitySelector, entitySelectorText).click(); 319 | } else { 320 | await page.locator(entitySelector).click(); 321 | } 322 | 323 | await expect(moreInfoDialog).toBeVisible(); 324 | 325 | for (const selector of selectors) { 326 | await expect(page.locator(selector)).toBeHidden(); 327 | } 328 | 329 | await goToPageWithParams(page, URL_PARAMS.CLEAR_KM_CACHE); 330 | 331 | await expect(masonryView).toBeVisible(); 332 | 333 | if (entitySelectorText) { 334 | await page.locator(entitySelector, entitySelectorText).click(); 335 | } else { 336 | await page.locator(entitySelector).click(); 337 | } 338 | 339 | await expect(moreInfoDialog).toBeVisible(); 340 | 341 | for (const selector of selectors) { 342 | await expect(page.locator(selector)).toBeVisible(); 343 | } 344 | 345 | }); 346 | 347 | }); 348 | 349 | test('Caching URL Parameter: ?block_overflow', async ({ page }) => { 350 | 351 | const masonryView = page.locator(SELECTORS.HUI_MASONRY_VIEW); 352 | const overflowButton = page.locator(SELECTORS.OVERFLOW_BUTTON); 353 | 354 | await goToPageWithParams(page, URL_PARAMS.BLOCK_OVERFLOW, URL_PARAMS.CACHE); 355 | 356 | await expect(masonryView).toBeVisible(); 357 | await expect(overflowButton).toHaveCSS('pointer-events', 'none'); 358 | 359 | await goToPage(page); 360 | 361 | await expect(masonryView).toBeVisible(); 362 | await expect(overflowButton).toHaveCSS('pointer-events', 'none'); 363 | 364 | await goToPageWithParams(page, URL_PARAMS.CLEAR_KM_CACHE); 365 | 366 | await expect(masonryView).toBeVisible(); 367 | await expect(overflowButton).not.toHaveCSS('pointer-events', 'none'); 368 | 369 | }); 370 | 371 | test('Caching URL Parameter: ?block_context_menu', async ({ context, page }) => { 372 | 373 | await context.addInitScript({ 374 | path: path.join(__dirname, '..', './node_modules/sinon/pkg/sinon.js'), 375 | }); 376 | 377 | await context.addInitScript(() => { 378 | window['__listener'] = window['sinon'].fake(); 379 | window.addEventListener('contextmenu', window['__listener']); 380 | }); 381 | 382 | await goToPageWithParams( 383 | page, 384 | URL_PARAMS.BLOCK_CONTEXT_MENU, 385 | URL_PARAMS.CACHE 386 | ); 387 | 388 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 389 | 390 | await page.locator(SELECTORS.HEADER).click({ 391 | button: 'right' 392 | }); 393 | 394 | let executed = await page.evaluate(() => window['__listener'].calledOnce); 395 | 396 | await expect(executed).toBe(false); 397 | 398 | await goToPage(page); 399 | 400 | await page.locator(SELECTORS.HEADER).click({ 401 | button: 'right' 402 | }); 403 | 404 | executed = await page.evaluate(() => window['__listener'].calledOnce); 405 | 406 | await expect(executed).toBe(false); 407 | 408 | await goToPageWithParams(page, URL_PARAMS.CLEAR_KM_CACHE); 409 | 410 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 411 | 412 | await page.locator(SELECTORS.HEADER).click({ 413 | button: 'right' 414 | }); 415 | 416 | executed = await page.evaluate(() => window['__listener'].calledOnce); 417 | 418 | await expect(executed).toBe(true); 419 | 420 | }); 421 | -------------------------------------------------------------------------------- /tests/02 - more-info-dialogs.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'playwright-test-coverage'; 2 | import { 3 | SELECTORS, 4 | DIALOGS_SELECTORS, 5 | TEXT_SELECTORS, 6 | ENTITIES 7 | } from './constants'; 8 | import { turnBooleanState, goToPage } from './utils'; 9 | 10 | test('Option: hide_dialog_header_breadcrumb_navigation', async ({ page }) => { 11 | 12 | await goToPage(page); 13 | 14 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.BINARY_SENSOR).click(); 15 | 16 | await expect(page.locator(DIALOGS_SELECTORS.BREADCRUMB_NAVIGATION)).toBeVisible(); 17 | 18 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HEADER_BREADCRUMB_NAVIGATION, true); 19 | 20 | await expect(page.locator(DIALOGS_SELECTORS.BREADCRUMB_NAVIGATION)).toBeHidden(); 21 | await expect(page).toHaveScreenshot('01-hide_dialog_header_breadcrumb_navigation.png'); 22 | 23 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HEADER_BREADCRUMB_NAVIGATION, false); 24 | 25 | }); 26 | 27 | test('Option: hide_dialog_header_history', async ({ page }) => { 28 | 29 | await goToPage(page); 30 | 31 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 32 | 33 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_BUTTON)).toBeVisible(); 34 | 35 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HISTORY_BUTTON, true); 36 | 37 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_BUTTON)).toBeHidden(); 38 | await expect(page).toHaveScreenshot('02-hide_dialog_header_history.png'); 39 | 40 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HISTORY_BUTTON, false); 41 | 42 | }); 43 | 44 | test('Option: hide_dialog_header_settings', async ({ page }) => { 45 | 46 | await goToPage(page); 47 | 48 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 49 | 50 | await expect(page.locator(DIALOGS_SELECTORS.SETTINGS_BUTTON)).toBeVisible(); 51 | 52 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_SETTINGS_BUTTON, true); 53 | 54 | await expect(page.locator(DIALOGS_SELECTORS.SETTINGS_BUTTON)).toBeHidden(); 55 | await expect(page).toHaveScreenshot('03-hide_dialog_header_settings.png'); 56 | 57 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_SETTINGS_BUTTON, false); 58 | 59 | }); 60 | 61 | test('Option: hide_dialog_header_overflow', async ({ page }) => { 62 | 63 | await goToPage(page); 64 | 65 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 66 | await expect(page.locator(DIALOGS_SELECTORS.OVERFLOW_BUTTON)).toBeVisible(); 67 | 68 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_OVERFLOW_BUTTON, true); 69 | 70 | await expect(page.locator(DIALOGS_SELECTORS.OVERFLOW_BUTTON)).toBeHidden(); 71 | await expect(page).toHaveScreenshot('04-hide_dialog_header_overflow.png'); 72 | 73 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_OVERFLOW_BUTTON, false); 74 | 75 | }); 76 | 77 | test('Option: hide_dialog_header_action_items', async ({ page }) => { 78 | 79 | await goToPage(page); 80 | 81 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 82 | 83 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_BUTTON)).toBeVisible(); 84 | await expect(page.locator(DIALOGS_SELECTORS.SETTINGS_BUTTON)).toBeVisible(); 85 | await expect(page.locator(DIALOGS_SELECTORS.OVERFLOW_BUTTON)).toBeVisible(); 86 | 87 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_ACTION_ITEMS, true); 88 | 89 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_BUTTON)).toBeHidden(); 90 | await expect(page.locator(DIALOGS_SELECTORS.SETTINGS_BUTTON)).toBeHidden(); 91 | await expect(page.locator(DIALOGS_SELECTORS.OVERFLOW_BUTTON)).toBeHidden(); 92 | await expect(page).toHaveScreenshot('05-hide_dialog_header_action_items.png'); 93 | 94 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_ACTION_ITEMS, false); 95 | 96 | }); 97 | 98 | test('Option: hide_dialog_history', async ({ page }) => { 99 | 100 | await goToPage(page); 101 | 102 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.HOME).click(); 103 | 104 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY)).toBeVisible(); 105 | 106 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HISTORY, true); 107 | 108 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY)).toBeHidden(); 109 | await expect(page).toHaveScreenshot('06-hide_dialog_history.png'); 110 | 111 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HISTORY, false); 112 | 113 | }); 114 | 115 | test('Option: hide_dialog_logbook', async ({ page }) => { 116 | 117 | await goToPage(page); 118 | 119 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.BINARY_SENSOR).click(); 120 | 121 | await expect(page.locator(DIALOGS_SELECTORS.LOGBOOK)).toBeVisible(); 122 | 123 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LOGBOOK, true); 124 | 125 | await expect(page.locator(DIALOGS_SELECTORS.LOGBOOK)).toBeHidden(); 126 | await expect(page).toHaveScreenshot('07-hide_dialog_logbook.png'); 127 | 128 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LOGBOOK, false); 129 | 130 | }); 131 | 132 | test('Option: hide_dialog_attributes', async ({ page }) => { 133 | 134 | await goToPage(page); 135 | 136 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.HOME).click(); 137 | 138 | await expect(page.locator(DIALOGS_SELECTORS.ATTRIBUTES)).toBeVisible(); 139 | 140 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_ATTRIBUTES, true); 141 | 142 | await expect(page.locator(DIALOGS_SELECTORS.ATTRIBUTES)).toBeHidden(); 143 | await expect(page).toHaveScreenshot('08-hide_dialog_attributes.png'); 144 | 145 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_ATTRIBUTES, false); 146 | 147 | }); 148 | 149 | test('Option: hide_dialog_update_actions', async ({ page }) => { 150 | 151 | await goToPage(page); 152 | 153 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 154 | 155 | await expect(page.locator(DIALOGS_SELECTORS.UPDATE_ACTIONS)).toBeVisible(); 156 | 157 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_UPDATE_ACTIONS, true); 158 | 159 | await expect(page.locator(DIALOGS_SELECTORS.UPDATE_ACTIONS)).toBeHidden(); 160 | await expect(page).toHaveScreenshot('09-hide_dialog_update_actions.png'); 161 | 162 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_UPDATE_ACTIONS, false); 163 | 164 | }); 165 | 166 | test('Option: hide_dialog_camera_actions', async ({ page }) => { 167 | 168 | await goToPage(page); 169 | 170 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.CAMERA).click(); 171 | 172 | await expect(page.locator(DIALOGS_SELECTORS.CAMERA_ACTIONS)).toBeVisible(); 173 | 174 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CAMERA_ACTIONS, true); 175 | 176 | await expect(page.locator(DIALOGS_SELECTORS.CAMERA_ACTIONS)).toBeHidden(); 177 | 178 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CAMERA_ACTIONS, false); 179 | 180 | }); 181 | 182 | test('Option: hide_dialog_media_actions', async ({ page }) => { 183 | 184 | await goToPage(page); 185 | 186 | await page.locator(SELECTORS.MEDIA_PLAYER_ENTITY_ROW).click(); 187 | 188 | await expect(page.locator(DIALOGS_SELECTORS.MEDIA_ACTIONS_MAIN)).toBeVisible(); 189 | await expect(page.locator(DIALOGS_SELECTORS.MEDIA_ACTIONS_SECONDARY)).toBeVisible(); 190 | 191 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_MEDIA_ACTIONS, true); 192 | 193 | await expect(page.locator(DIALOGS_SELECTORS.MEDIA_ACTIONS_MAIN)).toBeHidden(); 194 | await expect(page.locator(DIALOGS_SELECTORS.MEDIA_ACTIONS_SECONDARY)).toBeHidden(); 195 | await expect(page).toHaveScreenshot('10-hide_dialog_media_actions.png'); 196 | 197 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_MEDIA_ACTIONS, false); 198 | 199 | }); 200 | 201 | test('Option: hide_dialog_climate_actions, hide_dialog_climate_temperature_actions and hide_dialog_climate_settings_actions', async ({ page }) => { 202 | 203 | await goToPage(page); 204 | 205 | await page.locator(SELECTORS.CLIMATE_ENTITY_ROW, TEXT_SELECTORS.CLIMATE).click(); 206 | 207 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeVisible(); 208 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeVisible(); 209 | 210 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CLIMATE_ACTIONS, true); 211 | 212 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeHidden(); 213 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeHidden(); 214 | await expect(page).toHaveScreenshot('11-hide_dialog_climate_actions.png'); 215 | 216 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CLIMATE_ACTIONS, false); 217 | 218 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeVisible(); 219 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeVisible(); 220 | 221 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS, true); 222 | 223 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeHidden(); 224 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeVisible(); 225 | await expect(page).toHaveScreenshot('12-hide_dialog_climate_temperature_actions.png'); 226 | 227 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS, false); 228 | 229 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeVisible(); 230 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeVisible(); 231 | 232 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS, true); 233 | 234 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeVisible(); 235 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeHidden(); 236 | await expect(page).toHaveScreenshot('13-hide_dialog_climate_settings_actions.png'); 237 | 238 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS, false); 239 | 240 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeVisible(); 241 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeVisible(); 242 | 243 | }); 244 | 245 | test('Option: hide_dialog_light_actions, hide_dialog_light_control_actions, hide_dialog_light_color_actions and hide_dialog_light_settings_actions', async ({ page }) => { 246 | 247 | await goToPage(page); 248 | 249 | await page.locator(SELECTORS.TOGGLE_ENTITY_ROW, TEXT_SELECTORS.LIGHT).click(); 250 | 251 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 252 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 253 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 254 | 255 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_ACTIONS, true); 256 | 257 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeHidden(); 258 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeHidden(); 259 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeHidden(); 260 | await expect(page).toHaveScreenshot('14-hide_dialog_light_actions.png'); 261 | 262 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_ACTIONS, false); 263 | 264 | await page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS).scrollIntoViewIfNeeded(); 265 | 266 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS, true); 267 | 268 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeHidden(); 269 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 270 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 271 | await expect(page).toHaveScreenshot('15-hide_dialog_light_control_actions.png'); 272 | 273 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS, false); 274 | 275 | await page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS).scrollIntoViewIfNeeded(); 276 | 277 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 278 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 279 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 280 | 281 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_COLOR_ACTIONS, true); 282 | 283 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 284 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeHidden(); 285 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 286 | await expect(page).toHaveScreenshot('16-hide_dialog_light_color_actions.png'); 287 | 288 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_COLOR_ACTIONS, false); 289 | 290 | await page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS).scrollIntoViewIfNeeded(); 291 | 292 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 293 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 294 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 295 | 296 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS, true); 297 | 298 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 299 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 300 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeHidden(); 301 | await expect(page).toHaveScreenshot('17-hide_dialog_light_settings_actions.png'); 302 | 303 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS, false); 304 | 305 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 306 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 307 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 308 | 309 | }); 310 | 311 | test('Option: hide_dialog_timer_actions', async ({ page }) => { 312 | 313 | await goToPage(page); 314 | 315 | await page.locator(SELECTORS.TIMER_ENTITY_ROW, TEXT_SELECTORS.TIMER).click(); 316 | 317 | await expect(page.locator(DIALOGS_SELECTORS.TIMER_ACTIONS)).toBeVisible(); 318 | 319 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_TIMER_ACTIONS, true); 320 | 321 | await expect(page.locator(DIALOGS_SELECTORS.TIMER_ACTIONS)).toBeHidden(); 322 | await expect(page).toHaveScreenshot('18-hide_dialog_timer_actions.png'); 323 | 324 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_TIMER_ACTIONS, false); 325 | 326 | }); 327 | 328 | test('Option: hide_dialog_history_show_more', async ({ page }) => { 329 | 330 | await goToPage(page); 331 | 332 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.HOME).click(); 333 | 334 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_LINK)).toBeVisible(); 335 | 336 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HISTORY_SHOW_MORE, true); 337 | 338 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_LINK)).toBeHidden(); 339 | await expect(page).toHaveScreenshot('19-hide_dialog_history_show_more.png'); 340 | 341 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_HISTORY_SHOW_MORE, false); 342 | 343 | }); 344 | 345 | test('Option: hide_dialog_logbook_show_more', async ({ page }) => { 346 | 347 | await goToPage(page); 348 | 349 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.BINARY_SENSOR).click(); 350 | 351 | await expect(page.locator(DIALOGS_SELECTORS.LOGBOOK_LINK)).toBeVisible(); 352 | 353 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LOGBOOK_SHOW_MORE, true); 354 | 355 | await expect(page.locator(DIALOGS_SELECTORS.LOGBOOK_LINK)).toBeHidden(); 356 | await expect(page).toHaveScreenshot('20-hide_dialog_logbook_show_more.png'); 357 | 358 | await turnBooleanState(page, ENTITIES.HIDE_DIALOG_LOGBOOK_SHOW_MORE, false); 359 | 360 | }); -------------------------------------------------------------------------------- /tests/03 - url-parameters.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from 'playwright-test-coverage'; 2 | import path from 'path'; 3 | import { 4 | URL_PARAMS, 5 | SELECTORS, 6 | TEXT_SELECTORS, 7 | DIALOGS_SELECTORS, 8 | ENTITIES 9 | } from './constants'; 10 | import { goToPageWithParams, turnBooleanState } from './utils'; 11 | 12 | test('URL Parameter: ?kiosk', async ({ page }) => { 13 | 14 | await goToPageWithParams(page, URL_PARAMS.KIOSK); 15 | 16 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 17 | await expect(page.locator(SELECTORS.HEADER)).toBeHidden(); 18 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeHidden(); 19 | await expect(page).toHaveScreenshot('01-kiosk.png'); 20 | 21 | }); 22 | 23 | test('URL Parameter: ?hide_header', async ({ page }) => { 24 | 25 | await goToPageWithParams(page, URL_PARAMS.HIDE_HEADER); 26 | 27 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 28 | await expect(page.locator(SELECTORS.HEADER)).toBeHidden(); 29 | await expect(page).toHaveScreenshot('02-hide_header.png'); 30 | 31 | }); 32 | 33 | test('URL Parameter: ?hide_sidebar', async ({ page }) => { 34 | 35 | await goToPageWithParams(page, URL_PARAMS.HIDE_SIDEBAR); 36 | 37 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 38 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeHidden(); 39 | await expect(page).toHaveScreenshot('03-hide_sidebar.png'); 40 | 41 | }); 42 | 43 | test('URL Parameter: ?hide_menubutton', async ({ page }) => { 44 | 45 | await goToPageWithParams(page, URL_PARAMS.HIDE_MENU_BUTTON); 46 | 47 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 48 | await expect(page.locator(SELECTORS.MENU_BUTTON)).toBeHidden(); 49 | await expect(page).toHaveScreenshot('04-hide_menubutton.png'); 50 | 51 | }); 52 | 53 | test('URL Parameter: ?hide_notifications', async ({ page }) => { 54 | 55 | await goToPageWithParams(page, URL_PARAMS.HIDE_NOTIFICATIONS); 56 | 57 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 58 | await expect(page.locator(SELECTORS.NOTIFICATIONS)).toBeHidden(); 59 | await expect(page).toHaveScreenshot('05-hide_notifications.png'); 60 | 61 | }); 62 | 63 | test('URL Parameter: ?hide_account', async ({ page }) => { 64 | 65 | await goToPageWithParams(page, URL_PARAMS.HIDE_ACCOUNT); 66 | 67 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 68 | await expect(page.locator(SELECTORS.ACCOUNT)).toBeHidden(); 69 | await expect(page).toHaveScreenshot('06-hide_account.png'); 70 | 71 | }); 72 | 73 | test('URL Parameter: ?hide_add_to_home_assistant', async ({ page }) => { 74 | 75 | await goToPageWithParams(page, URL_PARAMS.HIDE_ADD_TO_HOME_ASSISTANT); 76 | 77 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 78 | await expect(page.locator(SELECTORS.ADD_TO_HOME_ASSISTANT)).toBeHidden(); 79 | await expect(page).toHaveScreenshot('07-hide_add_to_home_assistant.png'); 80 | 81 | }); 82 | 83 | test('URL Parameter: ?hide_search', async ({ page }) => { 84 | 85 | await goToPageWithParams(page, URL_PARAMS.HIDE_SEARCH); 86 | 87 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 88 | await expect(page.locator(SELECTORS.SEARCH_BUTTON)).toBeHidden(); 89 | await expect(page).toHaveScreenshot('08-hide_search.png'); 90 | 91 | }); 92 | 93 | test('URL Parameter: ?hide_assistant', async ({ page }) => { 94 | 95 | await goToPageWithParams(page, URL_PARAMS.HIDE_ASSISTANT); 96 | 97 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 98 | await expect(page.locator(SELECTORS.ASSISTANT_BUTTON)).toBeHidden(); 99 | await expect(page).toHaveScreenshot('09-hide_assistant.png'); 100 | 101 | }); 102 | 103 | test('URL Parameter: ?hide_overflow', async ({ page }) => { 104 | 105 | await goToPageWithParams(page, URL_PARAMS.HIDE_OVERFLOW); 106 | 107 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 108 | await expect(page.locator(SELECTORS.OVERFLOW_BUTTON)).toBeHidden(); 109 | await expect(page).toHaveScreenshot('10-hide_overflow.png'); 110 | 111 | }); 112 | 113 | test('URL Parameter: ?hide_refresh', async ({ page }) => { 114 | 115 | await goToPageWithParams(page, URL_PARAMS.HIDE_REFRESH); 116 | 117 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 118 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 119 | await expect(page.locator(SELECTORS.MENU_REFRESH_ITEM)).toBeHidden(); 120 | await expect(page).toHaveScreenshot('11-hide_refresh.png'); 121 | 122 | }); 123 | 124 | test('URL Parameter: ?hide_unused_entities', async ({ page }) => { 125 | 126 | await goToPageWithParams(page, URL_PARAMS.HIDE_UNUSED_ENTITIES); 127 | 128 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 129 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 130 | await expect(page.locator(SELECTORS.MENU_UNUSED_ENTITIES_ITEM)).toBeHidden(); 131 | await expect(page).toHaveScreenshot('12-hide_unused_entities.png'); 132 | 133 | }); 134 | 135 | test('URL Parameter: ?hide_reload_resources', async ({ page }) => { 136 | 137 | await goToPageWithParams(page, URL_PARAMS.HIDE_RELOAD_RESOURCES); 138 | 139 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 140 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 141 | await expect(page.locator(SELECTORS.MENU_RELOAD_RESOURCES_ITEM)).toBeHidden(); 142 | await expect(page).toHaveScreenshot('13-hide_reload_resources.png'); 143 | 144 | }); 145 | 146 | test('URL Parameter: ?hide_edit_dashboard', async ({ page }) => { 147 | 148 | await goToPageWithParams(page, URL_PARAMS.HIDE_EDIT_DASHBOARD); 149 | 150 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 151 | await page.locator(SELECTORS.OVERFLOW_BUTTON).click(); 152 | await expect(page.locator(SELECTORS.MENU_EDIT_DASHBOARD_ITEM)).toBeHidden(); 153 | await expect(page).toHaveScreenshot('14-hide_edit_dashboard.png'); 154 | 155 | }); 156 | 157 | test('URL Parameter: ?block_overflow', async ({ page }) => { 158 | 159 | await goToPageWithParams(page, URL_PARAMS.BLOCK_OVERFLOW); 160 | 161 | await expect(page.locator(SELECTORS.HUI_MASONRY_VIEW)).toBeVisible(); 162 | await expect(page.locator(SELECTORS.OVERFLOW_BUTTON)).toHaveCSS('pointer-events', 'none'); 163 | 164 | }); 165 | 166 | test('URL Parameter: ?block_context_menu', async ({ context, page }) => { 167 | 168 | await context.addInitScript({ 169 | path: path.join(__dirname, '..', './node_modules/sinon/pkg/sinon.js'), 170 | }); 171 | 172 | await context.addInitScript(() => { 173 | window['__listener'] = window['sinon'].fake(); 174 | window.addEventListener('contextmenu', window['__listener']); 175 | }); 176 | 177 | await goToPageWithParams(page, URL_PARAMS.BLOCK_CONTEXT_MENU); 178 | 179 | await expect(page.locator(SELECTORS.HEADER)).toBeVisible(); 180 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeVisible(); 181 | 182 | await page.locator(SELECTORS.HEADER).click({ 183 | button: 'right' 184 | }); 185 | 186 | const executed = await page.evaluate(() => window['__listener'].calledOnce); 187 | 188 | await expect(executed).toBe(false); 189 | 190 | }); 191 | 192 | test('URL Parameter: ?disable_km', async ({ page }) => { 193 | 194 | await goToPageWithParams(page, URL_PARAMS.DISABLE_KM); 195 | 196 | await expect(page.locator(SELECTORS.HEADER)).toBeVisible(); 197 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeVisible(); 198 | 199 | await turnBooleanState(page, ENTITIES.KIOSK, true); 200 | 201 | await expect(page.locator(SELECTORS.HEADER)).not.toBeHidden(); 202 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).not.toBeHidden(); 203 | 204 | await turnBooleanState(page, ENTITIES.KIOSK, false); 205 | 206 | await expect(page.locator(SELECTORS.HEADER)).toBeVisible(); 207 | await expect(page.locator(SELECTORS.HA_SIDEBAR)).toBeVisible(); 208 | 209 | }); 210 | 211 | test('URL Parameter: ?hide_dialog_header_breadcrumb_navigation', async ({ page }) => { 212 | 213 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_HEADER_BREADCRUMB_NAVIGATION); 214 | 215 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.BINARY_SENSOR).click(); 216 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 217 | await expect(page.locator(DIALOGS_SELECTORS.BREADCRUMB_NAVIGATION)).toBeHidden(); 218 | await expect(page).toHaveScreenshot('15-hide_dialog_header_breadcrumb_navigation.png'); 219 | 220 | }); 221 | 222 | test('URL Parameter: ?hide_dialog_header_history', async ({ page }) => { 223 | 224 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_HEADER_HISTORY); 225 | 226 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 227 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 228 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_BUTTON)).toBeHidden(); 229 | await expect(page).toHaveScreenshot('16-hide_dialog_header_history.png'); 230 | 231 | }); 232 | 233 | test('URL Parameter: ?hide_dialog_header_settings', async ({ page }) => { 234 | 235 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_HEADER_SETTINGS); 236 | 237 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 238 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 239 | await expect(page.locator(DIALOGS_SELECTORS.SETTINGS_BUTTON)).toBeHidden(); 240 | await expect(page).toHaveScreenshot('17-hide_dialog_header_settings.png'); 241 | 242 | }); 243 | 244 | test('URL Parameter: ?hide_dialog_header_overflow', async ({ page }) => { 245 | 246 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_HEADER_OVERFLOW); 247 | 248 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 249 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 250 | await expect(page.locator(DIALOGS_SELECTORS.OVERFLOW_BUTTON)).toBeHidden(); 251 | await expect(page).toHaveScreenshot('18-hide_dialog_header_overflow.png'); 252 | 253 | }); 254 | 255 | test('URL Parameter: ?hide_dialog_header_action_items', async ({ page }) => { 256 | 257 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_HEADER_ACTION_ITEMS); 258 | 259 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 260 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 261 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_BUTTON)).toBeHidden(); 262 | await expect(page.locator(DIALOGS_SELECTORS.SETTINGS_BUTTON)).toBeHidden(); 263 | await expect(page.locator(DIALOGS_SELECTORS.OVERFLOW_BUTTON)).toBeHidden(); 264 | await expect(page).toHaveScreenshot('19-hide_dialog_header_action_items.png'); 265 | 266 | }); 267 | 268 | test('URL Parameter: ?hide_dialog_history', async ({ page }) => { 269 | 270 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_HISTORY); 271 | 272 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.HOME).click(); 273 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 274 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY)).toBeHidden(); 275 | await expect(page).toHaveScreenshot('20-hide_dialog_history.png'); 276 | 277 | }); 278 | 279 | test('URL Parameter: ?hide_dialog_logbook', async ({ page }) => { 280 | 281 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_LOGBOOK); 282 | 283 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.BINARY_SENSOR).click(); 284 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 285 | await expect(page.locator(DIALOGS_SELECTORS.LOGBOOK)).toBeHidden(); 286 | await expect(page).toHaveScreenshot('21-hide_dialog_logbook.png'); 287 | 288 | }); 289 | 290 | test('URL Parameter: ?hide_dialog_attributes', async ({ page }) => { 291 | 292 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_ATTRIBUTES); 293 | 294 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.HOME).click(); 295 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 296 | await expect(page.locator(DIALOGS_SELECTORS.ATTRIBUTES)).toBeHidden(); 297 | await expect(page).toHaveScreenshot('22-hide_dialog_attributes.png'); 298 | 299 | }); 300 | 301 | test('URL Parameter: ?hide_dialog_update_actions', async ({ page }) => { 302 | 303 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_UPDATE_ACTIONS); 304 | 305 | await page.locator(SELECTORS.UPDATE_ENTITY_ROW, TEXT_SELECTORS.ADDON).click(); 306 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 307 | await expect(page.locator(DIALOGS_SELECTORS.UPDATE_ACTIONS)).toBeHidden(); 308 | await expect(page).toHaveScreenshot('23-hide_dialog_update_actions.png'); 309 | 310 | }); 311 | 312 | test('URL Parameter: ?hide_dialog_camera_actions', async ({ page }) => { 313 | 314 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_CAMERA_ACTIONS); 315 | 316 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.CAMERA).click(); 317 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 318 | await expect(page.locator(DIALOGS_SELECTORS.CAMERA_ACTIONS)).toBeHidden(); 319 | 320 | }); 321 | 322 | test('URL Parameter: ?hide_dialog_media_actions', async ({ page }) => { 323 | 324 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_MEDIA_ACTIONS); 325 | 326 | await page.locator(SELECTORS.MEDIA_PLAYER_ENTITY_ROW).click(); 327 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 328 | await expect(page.locator(DIALOGS_SELECTORS.MEDIA_ACTIONS)).toBeHidden(); 329 | await expect(page).toHaveScreenshot('24-hide_dialog_media_actions.png'); 330 | 331 | }); 332 | 333 | test('URL Parameter: ?hide_dialog_climate_actions', async ({ page }) => { 334 | 335 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_CLIMATE_ACTIONS); 336 | 337 | await page.locator(SELECTORS.CLIMATE_ENTITY_ROW, TEXT_SELECTORS.CLIMATE).click(); 338 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 339 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeHidden(); 340 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeHidden(); 341 | await expect(page).toHaveScreenshot('25-hide_dialog_climate_actions.png'); 342 | 343 | }); 344 | 345 | test('URL Parameter: ?hide_dialog_climate_temperature_actions', async ({ page }) => { 346 | 347 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_CLIMATE_TEMPERATURE_ACTIONS); 348 | 349 | await page.locator(SELECTORS.CLIMATE_ENTITY_ROW, TEXT_SELECTORS.CLIMATE).click(); 350 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 351 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeHidden(); 352 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeVisible(); 353 | await expect(page).toHaveScreenshot('26-hide_dialog_climate_temperature_actions.png'); 354 | 355 | }); 356 | 357 | test('URL Parameter: ?hide_dialog_climate_settings_actions', async ({ page }) => { 358 | 359 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_CLIMATE_SETTINGS_ACTIONS); 360 | 361 | await page.locator(SELECTORS.CLIMATE_ENTITY_ROW, TEXT_SELECTORS.CLIMATE).click(); 362 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 363 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_TEMPERATURE_BUTTONS)).toBeVisible(); 364 | await expect(page.locator(DIALOGS_SELECTORS.CLIMATE_SETTINGS_BUTTONS)).toBeHidden(); 365 | await expect(page).toHaveScreenshot('27-hide_dialog_climate_settings_actions.png'); 366 | 367 | }); 368 | 369 | test('URL Parameter: ?hide_dialog_light_actions', async ({ page }) => { 370 | 371 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_LIGHT_ACTIONS); 372 | 373 | await page.locator(SELECTORS.TOGGLE_ENTITY_ROW, TEXT_SELECTORS.LIGHT).click(); 374 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 375 | 376 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeHidden(); 377 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeHidden(); 378 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeHidden(); 379 | await expect(page).toHaveScreenshot('28-hide_dialog_light_actions.png'); 380 | 381 | }); 382 | 383 | test('URL Parameter: ?hide_dialog_light_control_actions', async ({ page }) => { 384 | 385 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_LIGHT_CONTROL_ACTIONS); 386 | 387 | await page.locator(SELECTORS.TOGGLE_ENTITY_ROW, TEXT_SELECTORS.LIGHT).click(); 388 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 389 | 390 | await page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS).scrollIntoViewIfNeeded(); 391 | 392 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeHidden(); 393 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 394 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 395 | await expect(page).toHaveScreenshot('29-hide_dialog_light_control_actions.png'); 396 | 397 | }); 398 | 399 | test('URL Parameter: ?hide_dialog_light_color_actions', async ({ page }) => { 400 | 401 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_LIGHT_COLOR_ACTIONS); 402 | 403 | await page.locator(SELECTORS.TOGGLE_ENTITY_ROW, TEXT_SELECTORS.LIGHT).click(); 404 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 405 | 406 | await page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS).scrollIntoViewIfNeeded(); 407 | 408 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 409 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeHidden(); 410 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeVisible(); 411 | await expect(page).toHaveScreenshot('30-hide_dialog_light_color_actions.png'); 412 | 413 | }); 414 | 415 | test('URL Parameter: ?hide_dialog_light_settings_actions', async ({ page }) => { 416 | 417 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_LIGHT_SETTINGS_ACTIONS); 418 | 419 | await page.locator(SELECTORS.TOGGLE_ENTITY_ROW, TEXT_SELECTORS.LIGHT).click(); 420 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 421 | 422 | await page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS).scrollIntoViewIfNeeded(); 423 | 424 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_CONTROL_ACTIONS)).toBeVisible(); 425 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_COLOR_ACTIONS)).toBeVisible(); 426 | await expect(page.locator(DIALOGS_SELECTORS.LIGHT_SETTINGS_ACTIONS)).toBeHidden(); 427 | await expect(page).toHaveScreenshot('31-hide_dialog_light_settings_actions.png'); 428 | 429 | }); 430 | 431 | test('URL Parameter: ?hide_dialog_timer_actions', async ({ page }) => { 432 | 433 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_TIMER_ACTIONS); 434 | 435 | await page.locator(SELECTORS.TIMER_ENTITY_ROW, TEXT_SELECTORS.TIMER).click(); 436 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 437 | await expect(page.locator(DIALOGS_SELECTORS.TIMER_ACTIONS)).toBeHidden(); 438 | await expect(page).toHaveScreenshot('32-hide_dialog_timer_actions.png'); 439 | 440 | }); 441 | 442 | test('URL Parameter: ?hide_dialog_history_show_more', async ({ page }) => { 443 | 444 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_HISTORY_SHOW_MORE); 445 | 446 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.HOME).click(); 447 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 448 | await expect(page.locator(DIALOGS_SELECTORS.HISTORY_LINK)).toBeHidden(); 449 | await expect(page).toHaveScreenshot('33-hide_dialog_history_show_more.png'); 450 | 451 | }); 452 | 453 | test('URL Parameter: ?hide_dialog_logbook_show_more', async ({ page }) => { 454 | 455 | await goToPageWithParams(page, URL_PARAMS.HIDE_DIALOG_LOGBOOK_SHOW_MORE); 456 | 457 | await page.locator(SELECTORS.ENTITY_ROW, TEXT_SELECTORS.BINARY_SENSOR).click(); 458 | await expect(page.locator(DIALOGS_SELECTORS.MORE_INFO_INFO)).toBeVisible(); 459 | await expect(page.locator(DIALOGS_SELECTORS.LOGBOOK_LINK)).toBeHidden(); 460 | await expect(page).toHaveScreenshot('34-hide_dialog_logbook_show_more.png'); 461 | 462 | }); --------------------------------------------------------------------------------