diff --git a/README.md b/README.md
index 6cd7a21a..13ceebe5 100644
--- a/README.md
+++ b/README.md
@@ -184,7 +184,7 @@ The following devices have been tested and confirmed as working. If your device
- **Plugs**: EP10, EP25\*, HS100\*\*, HS103, HS105, HS110, KP100, KP105, KP115, KP125, KP125M\*, KP401
- **Power Strips**: EP40, EP40M\*, HS107, HS300, KP200, KP303, KP400
-- **Wall Switches**: ES20M, HS200, HS210, HS220\*\*, KP405, KS200M, KS205\*, KS220, KS220M, KS225\*, KS230, KS240\*
+- **Wall Switches**: ES20M, HS200\*\*, HS210, HS220\*\*, KP405, KS200M, KS205\*, KS220, KS220M, KS225\*, KS230, KS240\*
- **Bulbs**: KL110, KL120, KL125, KL130, KL135, KL50, KL60, LB110
- **Light Strips**: KL400L5, KL420L5, KL430
- **Hubs**: KH100\*
@@ -192,7 +192,7 @@ The following devices have been tested and confirmed as working. If your device
### Supported Tapo\* devices
-- **Plugs**: P100, P110, P115, P125M, P135, TP15
+- **Plugs**: P100, P110, P110M, P115, P125M, P135, TP15
- **Power Strips**: P300, P304M, TP25
- **Wall Switches**: S500D, S505, S505D
- **Bulbs**: L510B, L510E, L530E, L630
diff --git a/SUPPORTED.md b/SUPPORTED.md
index ca207a03..349bed95 100644
--- a/SUPPORTED.md
+++ b/SUPPORTED.md
@@ -86,6 +86,7 @@ Some newer Kasa devices require authentication. These are marked with *\*
- **HS210**
- Hardware: 1.0 (US) / Firmware: 1.5.8
- Hardware: 2.0 (US) / Firmware: 1.1.5
@@ -186,6 +187,8 @@ All Tapo devices require authentication.
Hub-Connected Devices may work acros
- Hardware: 1.0 (EU) / Firmware: 1.0.7
- Hardware: 1.0 (EU) / Firmware: 1.2.3
- Hardware: 1.0 (UK) / Firmware: 1.3.0
+- **P110M**
+ - Hardware: 1.0 (AU) / Firmware: 1.2.3
- **P115**
- Hardware: 1.0 (EU) / Firmware: 1.2.3
- **P125M**
diff --git a/pyproject.toml b/pyproject.toml
index 9fdc888d..f9dfbf87 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -107,7 +107,9 @@ markers = [
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
timeout = 10
-addopts = "--disable-socket --allow-unix-socket"
+# dist=loadgroup enables grouping of tests into single worker.
+# required as caplog doesn't play nicely with multiple workers.
+addopts = "--disable-socket --allow-unix-socket --dist=loadgroup"
[tool.doc8]
paths = ["docs"]
diff --git a/tests/device_fixtures.py b/tests/device_fixtures.py
index 917f1998..2af0ca06 100644
--- a/tests/device_fixtures.py
+++ b/tests/device_fixtures.py
@@ -84,6 +84,7 @@ PLUGS_IOT = {
PLUGS_SMART = {
"P100",
"P110",
+ "P110M",
"P115",
"KP125M",
"EP25",
@@ -100,6 +101,7 @@ SWITCHES_IOT = {
"KS200M",
}
SWITCHES_SMART = {
+ "HS200",
"KS205",
"KS225",
"KS240",
@@ -124,7 +126,7 @@ SENSORS_SMART = {"T310", "T315", "T300", "T100", "T110", "S200B", "S200D"}
THERMOSTATS_SMART = {"KE100"}
WITH_EMETER_IOT = {"HS110", "HS300", "KP115", "KP125", *BULBS_IOT}
-WITH_EMETER_SMART = {"P110", "P115", "KP125M", "EP25", "P304M"}
+WITH_EMETER_SMART = {"P110", "P110M", "P115", "KP125M", "EP25", "P304M"}
WITH_EMETER = {*WITH_EMETER_IOT, *WITH_EMETER_SMART}
DIMMABLE = {*BULBS, *DIMMERS}
diff --git a/tests/fixtures/smart/HS200(US)_5.26_1.0.3.json b/tests/fixtures/smart/HS200(US)_5.26_1.0.3.json
new file mode 100644
index 00000000..e67435a9
--- /dev/null
+++ b/tests/fixtures/smart/HS200(US)_5.26_1.0.3.json
@@ -0,0 +1,379 @@
+{
+ "component_nego": {
+ "component_list": [
+ {
+ "id": "device",
+ "ver_code": 2
+ },
+ {
+ "id": "firmware",
+ "ver_code": 2
+ },
+ {
+ "id": "quick_setup",
+ "ver_code": 3
+ },
+ {
+ "id": "time",
+ "ver_code": 1
+ },
+ {
+ "id": "wireless",
+ "ver_code": 1
+ },
+ {
+ "id": "schedule",
+ "ver_code": 2
+ },
+ {
+ "id": "countdown",
+ "ver_code": 2
+ },
+ {
+ "id": "antitheft",
+ "ver_code": 1
+ },
+ {
+ "id": "account",
+ "ver_code": 1
+ },
+ {
+ "id": "synchronize",
+ "ver_code": 1
+ },
+ {
+ "id": "sunrise_sunset",
+ "ver_code": 1
+ },
+ {
+ "id": "led",
+ "ver_code": 3
+ },
+ {
+ "id": "cloud_connect",
+ "ver_code": 1
+ },
+ {
+ "id": "iot_cloud",
+ "ver_code": 1
+ },
+ {
+ "id": "device_local_time",
+ "ver_code": 1
+ },
+ {
+ "id": "default_states",
+ "ver_code": 1
+ },
+ {
+ "id": "auto_off",
+ "ver_code": 2
+ },
+ {
+ "id": "delay_action",
+ "ver_code": 2
+ },
+ {
+ "id": "smart_switch",
+ "ver_code": 1
+ }
+ ]
+ },
+ "discovery_result": {
+ "device_id": "00000000000000000000000000000000",
+ "device_model": "HS200(US)",
+ "device_type": "SMART.KASASWITCH",
+ "factory_default": false,
+ "ip": "127.0.0.123",
+ "is_support_iot_cloud": true,
+ "mac": "74-FE-CE-00-00-00",
+ "mgt_encrypt_schm": {
+ "encrypt_type": "KLAP",
+ "http_port": 80,
+ "is_support_https": false,
+ "lv": 2
+ },
+ "obd_src": "tplink",
+ "owner": "00000000000000000000000000000000"
+ },
+ "get_antitheft_rules": {
+ "antitheft_rule_max_count": 1,
+ "enable": false,
+ "rule_list": []
+ },
+ "get_auto_off_config": {
+ "delay_min": 120,
+ "enable": false
+ },
+ "get_auto_update_info": {
+ "enable": true,
+ "random_range": 120,
+ "time": 180
+ },
+ "get_connect_cloud_state": {
+ "status": 0
+ },
+ "get_countdown_rules": {
+ "countdown_rule_max_count": 1,
+ "enable": false,
+ "rule_list": []
+ },
+ "get_device_info": {
+ "auto_off_remain_time": 0,
+ "auto_off_status": "off",
+ "avatar": "hang_lamp_1",
+ "default_states": {
+ "state": {
+ "on": false
+ },
+ "type": "custom"
+ },
+ "device_id": "0000000000000000000000000000000000000000",
+ "device_on": false,
+ "fw_id": "00000000000000000000000000000000",
+ "fw_ver": "1.0.3 Build 240723 Rel.192622",
+ "has_set_location_info": true,
+ "hw_id": "00000000000000000000000000000000",
+ "hw_ver": "5.26",
+ "ip": "127.0.0.123",
+ "lang": "en_US",
+ "latitude": 0,
+ "longitude": 0,
+ "mac": "74-FE-CE-00-00-00",
+ "model": "HS200",
+ "nickname": "I01BU0tFRF9OQU1FIw==",
+ "oem_id": "00000000000000000000000000000000",
+ "on_time": 0,
+ "overheat_status": "normal",
+ "region": "America/New_York",
+ "rssi": -56,
+ "signal_level": 2,
+ "smart_switch_state": false,
+ "specs": "",
+ "ssid": "I01BU0tFRF9TU0lEIw==",
+ "time_diff": -300,
+ "type": "SMART.KASASWITCH"
+ },
+ "get_device_time": {
+ "region": "America/New_York",
+ "time_diff": -300,
+ "timestamp": 1732300703
+ },
+ "get_device_usage": {
+ "time_usage": {
+ "past30": 185,
+ "past7": 185,
+ "today": 0
+ }
+ },
+ "get_fw_download_state": {
+ "auto_upgrade": false,
+ "download_progress": 0,
+ "reboot_time": 5,
+ "status": 0,
+ "upgrade_time": 5
+ },
+ "get_latest_fw": {
+ "fw_size": 0,
+ "fw_ver": "1.0.3 Build 240723 Rel.192622",
+ "hw_id": "",
+ "need_to_upgrade": false,
+ "oem_id": "",
+ "release_date": "",
+ "release_note": "",
+ "type": 0
+ },
+ "get_led_info": {
+ "bri_config": {
+ "bri_type": "overall",
+ "overall_bri": 50
+ },
+ "led_rule": "toggle",
+ "led_status": true,
+ "night_mode": {
+ "end_time": 420,
+ "night_mode_type": "custom",
+ "start_time": 1320
+ }
+ },
+ "get_next_event": {},
+ "get_schedule_rules": {
+ "enable": false,
+ "rule_list": [],
+ "schedule_rule_max_count": 32,
+ "start_index": 0,
+ "sum": 0
+ },
+ "get_wireless_scan_info": {
+ "ap_list": [
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 3,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 3,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 2,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 2,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 0,
+ "key_type": "none",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 0,
+ "key_type": "none",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ }
+ ],
+ "start_index": 0,
+ "sum": 17,
+ "wep_supported": false
+ },
+ "qs_component_nego": {
+ "component_list": [
+ {
+ "id": "quick_setup",
+ "ver_code": 3
+ },
+ {
+ "id": "sunrise_sunset",
+ "ver_code": 1
+ },
+ {
+ "id": "iot_cloud",
+ "ver_code": 1
+ },
+ {
+ "id": "inherit",
+ "ver_code": 1
+ },
+ {
+ "id": "firmware",
+ "ver_code": 2
+ }
+ ],
+ "extra_info": {
+ "device_model": "HS200",
+ "device_type": "SMART.KASASWITCH",
+ "is_klap": true
+ }
+ }
+}
diff --git a/tests/fixtures/smart/P110M(AU)_1.0_1.2.3.json b/tests/fixtures/smart/P110M(AU)_1.0_1.2.3.json
new file mode 100644
index 00000000..efb88c85
--- /dev/null
+++ b/tests/fixtures/smart/P110M(AU)_1.0_1.2.3.json
@@ -0,0 +1,418 @@
+{
+ "component_nego": {
+ "component_list": [
+ {
+ "id": "device",
+ "ver_code": 2
+ },
+ {
+ "id": "firmware",
+ "ver_code": 2
+ },
+ {
+ "id": "quick_setup",
+ "ver_code": 3
+ },
+ {
+ "id": "time",
+ "ver_code": 1
+ },
+ {
+ "id": "wireless",
+ "ver_code": 1
+ },
+ {
+ "id": "schedule",
+ "ver_code": 2
+ },
+ {
+ "id": "countdown",
+ "ver_code": 2
+ },
+ {
+ "id": "antitheft",
+ "ver_code": 1
+ },
+ {
+ "id": "account",
+ "ver_code": 1
+ },
+ {
+ "id": "synchronize",
+ "ver_code": 1
+ },
+ {
+ "id": "sunrise_sunset",
+ "ver_code": 1
+ },
+ {
+ "id": "led",
+ "ver_code": 1
+ },
+ {
+ "id": "cloud_connect",
+ "ver_code": 1
+ },
+ {
+ "id": "iot_cloud",
+ "ver_code": 1
+ },
+ {
+ "id": "device_local_time",
+ "ver_code": 1
+ },
+ {
+ "id": "default_states",
+ "ver_code": 1
+ },
+ {
+ "id": "auto_off",
+ "ver_code": 2
+ },
+ {
+ "id": "localSmart",
+ "ver_code": 1
+ },
+ {
+ "id": "energy_monitoring",
+ "ver_code": 2
+ },
+ {
+ "id": "power_protection",
+ "ver_code": 1
+ },
+ {
+ "id": "charging_protection",
+ "ver_code": 2
+ },
+ {
+ "id": "matter",
+ "ver_code": 2
+ },
+ {
+ "id": "current_protection",
+ "ver_code": 1
+ }
+ ]
+ },
+ "discovery_result": {
+ "device_id": "00000000000000000000000000000000",
+ "device_model": "P110M(AU)",
+ "device_type": "SMART.TAPOPLUG",
+ "factory_default": false,
+ "ip": "127.0.0.123",
+ "is_support_iot_cloud": true,
+ "mac": "F0-09-0D-00-00-00",
+ "mgt_encrypt_schm": {
+ "encrypt_type": "KLAP",
+ "http_port": 80,
+ "is_support_https": false,
+ "lv": 2
+ },
+ "obd_src": "tplink",
+ "owner": "00000000000000000000000000000000"
+ },
+ "get_auto_off_config": {
+ "delay_min": 120,
+ "enable": false
+ },
+ "get_auto_update_info": {
+ "enable": false,
+ "random_range": 120,
+ "time": 180
+ },
+ "get_connect_cloud_state": {
+ "status": 1
+ },
+ "get_energy_usage": {
+ "today_runtime": 306,
+ "month_runtime": 12572,
+ "today_energy": 173,
+ "month_energy": 6110,
+ "local_time": "2024-11-22 21:03:25",
+ "electricity_charge": [
+ 0,
+ 0,
+ 0
+ ],
+ "current_power": 74116
+ },
+ "get_current_power": {
+ "current_power": 74
+ },
+ "get_device_info": {
+ "auto_off_remain_time": 0,
+ "auto_off_status": "off",
+ "avatar": "plug",
+ "charging_status": "normal",
+ "default_states": {
+ "state": {},
+ "type": "last_states"
+ },
+ "device_id": "0000000000000000000000000000000000000000",
+ "device_on": true,
+ "fw_id": "00000000000000000000000000000000",
+ "fw_ver": "1.2.3 Build 240617 Rel.153525",
+ "has_set_location_info": false,
+ "hw_id": "00000000000000000000000000000000",
+ "hw_ver": "1.0",
+ "ip": "127.0.0.123",
+ "lang": "",
+ "latitude": 0,
+ "longitude": 0,
+ "mac": "F0-09-0D-00-00-00",
+ "model": "P110M",
+ "nickname": "I01BU0tFRF9OQU1FIw==",
+ "oem_id": "00000000000000000000000000000000",
+ "on_time": 186533,
+ "overcurrent_status": "normal",
+ "overheat_status": "normal",
+ "power_protection_status": "normal",
+ "region": "Australia/Sydney",
+ "rssi": -53,
+ "signal_level": 2,
+ "specs": "",
+ "ssid": "I01BU0tFRF9TU0lEIw==",
+ "time_diff": 600,
+ "type": "SMART.TAPOPLUG"
+ },
+ "get_device_time": {
+ "region": "Australia/Sydney",
+ "time_diff": 600,
+ "timestamp": 946958455
+ },
+ "get_electricity_price_config": {
+ "constant_price": 0,
+ "time_of_use_config": {
+ "summer": {
+ "midpeak": 0,
+ "offpeak": 0,
+ "onpeak": 0,
+ "period": [
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "weekday_config": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "weekend_config": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ },
+ "winter": {
+ "midpeak": 0,
+ "offpeak": 0,
+ "onpeak": 0,
+ "period": [
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "weekday_config": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ],
+ "weekend_config": [
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1
+ ]
+ }
+ },
+ "type": "constant"
+ },
+ "get_fw_download_state": {
+ "auto_upgrade": false,
+ "download_progress": 0,
+ "reboot_time": 5,
+ "status": 0,
+ "upgrade_time": 5
+ },
+ "get_led_info": {
+ "bri_config": {
+ "bri_type": "overall",
+ "overall_bri": 50
+ },
+ "led_rule": "always",
+ "led_status": true,
+ "night_mode": {
+ "end_time": 420,
+ "night_mode_type": "sunrise_sunset",
+ "start_time": 1140,
+ "sunrise_offset": 0,
+ "sunset_offset": 0
+ }
+ },
+ "get_matter_setup_info": {
+ "setup_code": "00000000000",
+ "setup_payload": "00:0000000000000000000"
+ },
+ "get_max_power": {
+ "max_power": 2465
+ },
+ "get_next_event": {},
+ "get_protection_power": {
+ "enabled": true,
+ "protection_power": 1120
+ },
+ "get_wireless_scan_info": {
+ "ap_list": [
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 3,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ },
+ {
+ "bssid": "000000000000",
+ "channel": 0,
+ "cipher_type": 2,
+ "key_type": "wpa2_psk",
+ "signal_level": 1,
+ "ssid": "I01BU0tFRF9TU0lEIw=="
+ }
+ ],
+ "start_index": 0,
+ "sum": 3,
+ "wep_supported": false
+ },
+ "qs_component_nego": {
+ "component_list": [
+ {
+ "id": "quick_setup",
+ "ver_code": 3
+ },
+ {
+ "id": "sunrise_sunset",
+ "ver_code": 1
+ },
+ {
+ "id": "ble_whole_setup",
+ "ver_code": 1
+ },
+ {
+ "id": "matter",
+ "ver_code": 2
+ },
+ {
+ "id": "iot_cloud",
+ "ver_code": 1
+ },
+ {
+ "id": "inherit",
+ "ver_code": 1
+ },
+ {
+ "id": "firmware",
+ "ver_code": 2
+ }
+ ],
+ "extra_info": {
+ "device_model": "P110M",
+ "device_type": "SMART.TAPOPLUG",
+ "is_klap": true
+ }
+ }
+}
diff --git a/tests/iot/modules/test_schedule.py b/tests/iot/modules/test_schedule.py
index 152aaac8..4a4ffdee 100644
--- a/tests/iot/modules/test_schedule.py
+++ b/tests/iot/modules/test_schedule.py
@@ -7,6 +7,7 @@ from ...device_fixtures import device_iot
@device_iot
+@pytest.mark.xdist_group(name="caplog")
def test_schedule(dev: Device, caplog: pytest.LogCaptureFixture):
schedule = dev.modules.get(Module.IotSchedule)
assert schedule
diff --git a/tests/smart/modules/test_firmware.py b/tests/smart/modules/test_firmware.py
index 0bc0a4ea..e3fe5bb3 100644
--- a/tests/smart/modules/test_firmware.py
+++ b/tests/smart/modules/test_firmware.py
@@ -90,6 +90,7 @@ async def test_update_available_without_cloud(dev: SmartDevice):
],
)
@pytest.mark.requires_dummy
+@pytest.mark.xdist_group(name="caplog")
async def test_firmware_update(
dev: SmartDevice,
mocker: MockerFixture,
diff --git a/tests/smart/modules/test_temperaturecontrol.py b/tests/smart/modules/test_temperaturecontrol.py
index 2653c53e..d47f19ee 100644
--- a/tests/smart/modules/test_temperaturecontrol.py
+++ b/tests/smart/modules/test_temperaturecontrol.py
@@ -137,6 +137,7 @@ async def test_thermostat_mode(dev, mode, states, frost_protection):
),
],
)
+@pytest.mark.xdist_group(name="caplog")
async def test_thermostat_mode_warnings(dev, mode, states, msg, caplog):
"""Test thermostat modes that should log a warning."""
temp_module: TemperatureControl = dev.modules["TemperatureControl"]
diff --git a/tests/test_aestransport.py b/tests/test_aestransport.py
index 4c95289a..64bc8d4e 100644
--- a/tests/test_aestransport.py
+++ b/tests/test_aestransport.py
@@ -216,6 +216,7 @@ async def test_send(mocker, status_code, error_code, inner_error_code, expectati
assert "result" in res
+@pytest.mark.xdist_group(name="caplog")
async def test_unencrypted_response(mocker, caplog):
host = "127.0.0.1"
mock_aes_device = MockAesDevice(host, 200, 0, 0, do_not_encrypt_response=True)
diff --git a/tests/test_bulb.py b/tests/test_bulb.py
index 4a547522..3ae1328f 100644
--- a/tests/test_bulb.py
+++ b/tests/test_bulb.py
@@ -232,6 +232,7 @@ async def test_set_color_temp_transition(dev: IotBulb, mocker):
@variable_temp_iot
+@pytest.mark.xdist_group(name="caplog")
async def test_unknown_temp_range(dev: IotBulb, monkeypatch, caplog):
monkeypatch.setitem(dev._sys_info, "model", "unknown bulb")
light = dev.modules.get(Module.Light)
diff --git a/tests/test_childdevice.py b/tests/test_childdevice.py
index d734d82c..1e525efb 100644
--- a/tests/test_childdevice.py
+++ b/tests/test_childdevice.py
@@ -136,6 +136,7 @@ async def test_child_time(dev: Device, freezer: FrozenDateTimeFactory):
assert child.time != fallback_time
+@pytest.mark.xdist_group(name="caplog")
async def test_child_device_type_unknown(caplog):
"""Test for device type when category is unknown."""
diff --git a/tests/test_device_factory.py b/tests/test_device_factory.py
index a0f501c3..86003744 100644
--- a/tests/test_device_factory.py
+++ b/tests/test_device_factory.py
@@ -109,6 +109,7 @@ async def test_connect_custom_port(discovery_mock, mocker, custom_port):
assert dev.port == custom_port or dev.port == default_port
+@pytest.mark.xdist_group(name="caplog")
async def test_connect_logs_connect_time(
discovery_mock,
caplog: pytest.LogCaptureFixture,
@@ -192,6 +193,7 @@ async def test_device_types(dev: Device):
assert dev.device_type == res
+@pytest.mark.xdist_group(name="caplog")
async def test_device_class_from_unknown_family(caplog):
"""Verify that unknown SMART devices yield a warning and fallback to SmartDevice."""
dummy_name = "SMART.foo"
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 787dea0e..7069e32f 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -119,6 +119,7 @@ async def test_type_detection_lightstrip(dev: Device):
assert d.device_type == DeviceType.LightStrip
+@pytest.mark.xdist_group(name="caplog")
async def test_type_unknown(caplog):
invalid_info = {"system": {"get_sysinfo": {"type": "nosuchtype"}}}
assert Discover._get_device_class(invalid_info) is IotPlug
@@ -586,6 +587,7 @@ async def test_do_discover_external_cancel(mocker):
await dp.wait_for_discovery_to_complete()
+@pytest.mark.xdist_group(name="caplog")
async def test_discovery_redaction(discovery_mock, caplog: pytest.LogCaptureFixture):
"""Test query sensitive info redaction."""
mac = "12:34:56:78:9A:BC"
diff --git a/tests/test_feature.py b/tests/test_feature.py
index 79560b1a..46cdd116 100644
--- a/tests/test_feature.py
+++ b/tests/test_feature.py
@@ -127,6 +127,7 @@ async def test_feature_action(mocker):
mock_call_action.assert_called()
+@pytest.mark.xdist_group(name="caplog")
async def test_feature_choice_list(dummy_feature, caplog, mocker: MockerFixture):
"""Test the choice feature type."""
dummy_feature.type = Feature.Type.Choice
diff --git a/tests/test_klapprotocol.py b/tests/test_klapprotocol.py
index a1521ee4..26d9f57a 100644
--- a/tests/test_klapprotocol.py
+++ b/tests/test_klapprotocol.py
@@ -184,6 +184,7 @@ async def test_protocol_reconnect(mocker, retry_count, protocol_class, transport
@pytest.mark.parametrize("log_level", [logging.WARNING, logging.DEBUG])
+@pytest.mark.xdist_group(name="caplog")
async def test_protocol_logging(mocker, caplog, log_level):
caplog.set_level(log_level)
logging.getLogger("kasa").setLevel(log_level)
diff --git a/tests/test_protocol.py b/tests/test_protocol.py
index 767d0f10..09134e85 100644
--- a/tests/test_protocol.py
+++ b/tests/test_protocol.py
@@ -307,6 +307,7 @@ async def test_protocol_handles_cancellation_during_connection(
ids=("_deprecated_TPLinkSmartHomeProtocol", "IotProtocol-XorTransport"),
)
@pytest.mark.parametrize("log_level", [logging.WARNING, logging.DEBUG])
+@pytest.mark.xdist_group(name="caplog")
async def test_protocol_logging(
mocker, caplog, log_level, protocol_class, transport_class, encryption_class
):
@@ -685,6 +686,7 @@ def test_deprecated_protocol():
@device_iot
+@pytest.mark.xdist_group(name="caplog")
async def test_iot_queries_redaction(dev: IotDevice, caplog: pytest.LogCaptureFixture):
"""Test query sensitive info redaction."""
if isinstance(dev.protocol._transport, FakeIotTransport):
diff --git a/tests/test_smartdevice.py b/tests/test_smartdevice.py
index 9d5956dc..a89b1098 100644
--- a/tests/test_smartdevice.py
+++ b/tests/test_smartdevice.py
@@ -27,6 +27,7 @@ from .conftest import (
@device_smart
@pytest.mark.requires_dummy
+@pytest.mark.xdist_group(name="caplog")
async def test_try_get_response(dev: SmartDevice, caplog):
mock_response: dict = {
"get_device_info": SmartErrorCode.PARAMS_ERROR,
@@ -143,6 +144,7 @@ async def test_update_module_queries(dev: SmartDevice, mocker: MockerFixture):
@device_smart
+@pytest.mark.xdist_group(name="caplog")
async def test_update_module_update_delays(
dev: SmartDevice,
mocker: MockerFixture,
@@ -203,6 +205,7 @@ async def test_update_module_update_delays(
],
)
@device_smart
+@pytest.mark.xdist_group(name="caplog")
async def test_update_module_query_errors(
dev: SmartDevice,
mocker: MockerFixture,
diff --git a/tests/test_smartprotocol.py b/tests/test_smartprotocol.py
index 180fb6aa..fce6cd07 100644
--- a/tests/test_smartprotocol.py
+++ b/tests/test_smartprotocol.py
@@ -54,6 +54,7 @@ async def test_smart_device_errors(dummy_protocol, mocker, error_code):
@pytest.mark.parametrize("error_code", [-13333, 13333])
+@pytest.mark.xdist_group(name="caplog")
async def test_smart_device_unknown_errors(
dummy_protocol, mocker, error_code, caplog: pytest.LogCaptureFixture
):
@@ -417,6 +418,7 @@ async def test_incomplete_list(mocker, caplog):
@device_smart
+@pytest.mark.xdist_group(name="caplog")
async def test_smart_queries_redaction(
dev: SmartDevice, caplog: pytest.LogCaptureFixture
):
diff --git a/tests/test_sslaestransport.py b/tests/test_sslaestransport.py
index 0d8fac9c..6816fa35 100644
--- a/tests/test_sslaestransport.py
+++ b/tests/test_sslaestransport.py
@@ -175,6 +175,7 @@ async def test_send(mocker):
assert "result" in res
+@pytest.mark.xdist_group(name="caplog")
async def test_unencrypted_response(mocker, caplog):
host = "127.0.0.1"
mock_ssl_aes_device = MockSslAesDevice(host, do_not_encrypt_response=True)