Skip to content

Commit 220c950

Browse files
committed
refactor(e2e_appium): migrate to cloud provider reporting and enhance configuration
- Replaced LambdaTestReporter with CloudResultReporter for unified cloud reporting. - Introduced BrowserStackProvider and LocalProvider - Updated conftest.py - Test result reporting to accommodate multiple cloud providers. - Removed deprecated LambdaTest reporting utilities and streamlined configuration management.
1 parent d3afb07 commit 220c950

File tree

9 files changed

+673
-213
lines changed

9 files changed

+673
-213
lines changed

test/e2e_appium/conftest.py

Lines changed: 115 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import pytest
33
from datetime import datetime
44
from pathlib import Path
5+
from typing import List
56

67
from .config import setup_logging, log_test_start, log_test_end
78
from .config.logging_config import get_logger
89
from .core import EnvironmentSwitcher
9-
from .utils.lambdatest_reporter import LambdaTestReporter
10+
from .utils.cloud_reporter import CloudResultReporter
1011
from .utils.screenshot import save_screenshot, save_page_source
1112

1213

@@ -17,6 +18,7 @@
1718

1819

1920
_logging_setup = None
21+
_saved_failure_logs: List[Path] = []
2022

2123

2224
def pytest_configure(config):
@@ -27,28 +29,27 @@ def pytest_configure(config):
2729
try:
2830
cli_env = getattr(config.option, "env", None)
2931
if cli_env:
30-
normalized_env = (
31-
"lambdatest" if cli_env in ("lt", "lambdatest") else "local"
32-
)
33-
os.environ["CURRENT_TEST_ENVIRONMENT"] = normalized_env
32+
os.environ["CURRENT_TEST_ENVIRONMENT"] = cli_env
3433
except Exception:
3534
# Do not block test runs if normalization fails
3635
pass
3736

3837
# Use YAML-based configuration
39-
env_name = os.getenv("CURRENT_TEST_ENVIRONMENT", "lambdatest")
38+
switcher = EnvironmentSwitcher()
39+
env_name = (
40+
os.getenv("CURRENT_TEST_ENVIRONMENT") or switcher.auto_detect_environment()
41+
)
4042

4143
try:
42-
switcher = EnvironmentSwitcher()
4344
env_config = switcher.switch_to(env_name)
4445

4546
# Use directories from YAML config
4647
reports_dir = Path(env_config.directories.get("reports", "reports"))
47-
enable_xml_report = env_config.logging_config.get("enable_xml_report", True)
48-
enable_html_report = env_config.logging_config.get("enable_html_report", True)
48+
enable_xml_report = env_config.logging.get("enable_xml_report", True)
49+
enable_html_report = env_config.logging.get("enable_html_report", True)
4950

5051
logger = get_logger("conftest")
51-
logger.info(f"📁 Using reports directory from {env_name} config: {reports_dir}")
52+
logger.info("Using reports directory from %s config: %s", env_name, reports_dir)
5253

5354
except Exception as e:
5455
# Simplified fallback using defaults
@@ -57,8 +58,8 @@ def pytest_configure(config):
5758
enable_html_report = True
5859

5960
logger = get_logger("conftest")
60-
logger.warning(f"⚠️ Using default configuration: {e}")
61-
logger.warning("💡 Ensure YAML config files are properly set up")
61+
logger.warning("Using default configuration: %s", e)
62+
logger.warning("Ensure YAML config files are properly set up")
6263

6364
reports_dir.mkdir(exist_ok=True)
6465

@@ -77,19 +78,19 @@ def pytest_configure(config):
7778

7879
logger = _logging_setup["loggers"]["main"] if _logging_setup else None
7980
if logger:
80-
logger.info("📊 Automatic report generation enabled:")
81+
logger.info("Automatic report generation enabled:")
8182
if hasattr(config.option, "xmlpath") and config.option.xmlpath:
82-
logger.info(f" 📄 XML Report: {config.option.xmlpath}")
83+
logger.info(" XML report: %s", config.option.xmlpath)
8384
if hasattr(config.option, "htmlpath") and config.option.htmlpath:
84-
logger.info(f" 🌐 HTML Report: {config.option.htmlpath}")
85+
logger.info(" HTML report: %s", config.option.htmlpath)
8586

8687

8788
def pytest_addoption(parser):
8889
parser.addoption(
8990
"--env",
9091
action="store",
91-
default="lt",
92-
help="Test environment: local or lt (LambdaTest)",
92+
default="browserstack",
93+
help="Test environment defined in config/environments (e.g. local, browserstack)",
9394
)
9495

9596

@@ -154,17 +155,19 @@ def pytest_runtest_makereport(item, call):
154155

155156
setattr(item, "rep_" + rep.when, rep)
156157

157-
# Report setup/call/teardown so setup failures are reflected in LT
158+
# Report setup/call/teardown so setup failures are reflected in the cloud dashboard
158159
if rep.when in ("setup", "call", "teardown"):
159160
try:
160-
LambdaTestReporter.report_test_result(item, rep)
161+
CloudResultReporter.report_test_result(item, rep)
161162
except Exception as e:
162163
logger = get_logger("session")
163-
logger.error(f"Failed to report test result to LambdaTest: {e}")
164+
logger.error(f"Failed to report test result to cloud provider: {e}")
164165

165166
# Get screenshot and page source artifacts
166167
try:
167-
if getattr(rep, "failed", False) and not getattr(item, "_failure_artifacts_saved", False):
168+
if getattr(rep, "failed", False) and not getattr(
169+
item, "_failure_artifacts_saved", False
170+
):
168171
driver = None
169172
try:
170173
if hasattr(item, "instance") and hasattr(item.instance, "driver"):
@@ -175,23 +178,36 @@ def pytest_runtest_makereport(item, call):
175178
if driver:
176179
# Resolve screenshots directory from environment config; fallback to 'screenshots'
177180
try:
178-
env_name = os.getenv("CURRENT_TEST_ENVIRONMENT", "lambdatest")
179-
switcher = EnvironmentSwitcher()
180-
env_config = switcher.switch_to(env_name)
181-
screenshots_dir = env_config.directories.get("screenshots", "screenshots")
181+
env_switcher = EnvironmentSwitcher()
182+
env_name = (
183+
os.getenv("CURRENT_TEST_ENVIRONMENT")
184+
or env_switcher.auto_detect_environment()
185+
)
186+
env_config = env_switcher.switch_to(env_name)
187+
screenshots_dir = env_config.directories.get(
188+
"screenshots", "screenshots"
189+
)
190+
logs_dir = env_config.directories.get("logs", "logs")
182191
except Exception:
183192
screenshots_dir = "screenshots"
193+
logs_dir = "logs"
184194

185-
test_id = getattr(item, "name", "test") + (f"__{rep.when}" if getattr(rep, "when", None) else "")
195+
test_id = getattr(item, "name", "test") + (
196+
f"__{rep.when}" if getattr(rep, "when", None) else ""
197+
)
186198

187199
s_path = None
188200
x_path = None
189201
try:
190-
s_path = save_screenshot(driver, str(screenshots_dir), f"FAILED_{test_id}")
202+
s_path = save_screenshot(
203+
driver, str(screenshots_dir), f"FAILED_{test_id}"
204+
)
191205
except Exception:
192206
pass
193207
try:
194-
x_path = save_page_source(driver, str(screenshots_dir), f"FAILED_{test_id}")
208+
x_path = save_page_source(
209+
driver, str(screenshots_dir), f"FAILED_{test_id}"
210+
)
195211
except Exception:
196212
pass
197213

@@ -201,6 +217,56 @@ def pytest_runtest_makereport(item, call):
201217
if x_path:
202218
log.info(f"Saved failure page source: {x_path}")
203219

220+
# Capture Appium server/logcat logs for deeper diagnostics
221+
log_paths: List[Path] = []
222+
try:
223+
log_types = set(getattr(driver, "log_types", []) or [])
224+
except Exception:
225+
log_types = set()
226+
227+
desired_types = [
228+
log_type
229+
for log_type in ("server", "logcat")
230+
if log_type in log_types
231+
]
232+
if desired_types:
233+
logs_root = Path(logs_dir)
234+
logs_root.mkdir(parents=True, exist_ok=True)
235+
for log_type in desired_types:
236+
try:
237+
entries = driver.get_log(log_type) or []
238+
except Exception as err:
239+
log.warning(
240+
f"Failed to fetch '{log_type}' log for {test_id}: {err}"
241+
)
242+
continue
243+
244+
if not entries:
245+
continue
246+
247+
log_file = logs_root / f"FAILED_{test_id}_{log_type}.log"
248+
try:
249+
with log_file.open("w", encoding="utf-8") as handle:
250+
for entry in entries:
251+
timestamp = (
252+
entry.get("timestamp")
253+
or entry.get("time")
254+
or ""
255+
)
256+
level = entry.get("level") or ""
257+
message = entry.get("message") or ""
258+
handle.write(f"{timestamp}\t{level}\t{message}\n")
259+
log.info(f"Saved failure {log_type} log: {log_file}")
260+
log_paths.append(log_file)
261+
except Exception as err:
262+
log.warning(
263+
f"Failed to persist {log_type} log for {test_id}: {err}"
264+
)
265+
266+
if log_paths:
267+
global _saved_failure_logs
268+
_saved_failure_logs.extend(log_paths)
269+
204270
setattr(item, "_failure_artifacts_saved", True)
205271
except Exception as e:
206272
log = get_logger("conftest")
@@ -221,37 +287,37 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
221287
total = passed + failed + skipped + errors
222288

223289
logger.info("=" * 60)
224-
logger.info("🎯 TEST EXECUTION SUMMARY")
290+
logger.info("TEST EXECUTION SUMMARY")
225291
logger.info("=" * 60)
226-
logger.info(f"Total Tests: {total}")
227-
logger.info(f"✅ Passed: {passed}")
228-
logger.info(f"❌ Failed: {failed}")
229-
logger.info(f"⏭️ Skipped: {skipped}")
230-
logger.info(f"💥 Errors: {errors}")
292+
logger.info("Total tests: %s", total)
293+
logger.info("Passed: %s", passed)
294+
logger.info("Failed: %s", failed)
295+
logger.info("Skipped: %s", skipped)
296+
logger.info("Errors: %s", errors)
231297

232298
if total > 0:
233299
success_rate = (passed / total) * 100
234-
logger.info(f"📊 Success Rate: {success_rate:.1f}%")
300+
logger.info("Success rate: %.1f%%", success_rate)
235301

236302
logger.info("Reports Generated:")
237303
if hasattr(config.option, "xmlpath") and config.option.xmlpath:
238304
xml_file = Path(config.option.xmlpath)
239305
if xml_file.exists():
240-
logger.info(f" 📄 XML Report: {xml_file}")
306+
logger.info(" XML report: %s", xml_file)
241307
else:
242-
logger.warning(f" ⚠️ XML Report expected but not found: {xml_file}")
308+
logger.warning(" XML report expected but not found: %s", xml_file)
243309

244310
if hasattr(config.option, "htmlpath") and config.option.htmlpath:
245311
html_file = Path(config.option.htmlpath)
246312
if html_file.exists():
247-
logger.info(f" 🌐 HTML Report: {html_file}")
313+
logger.info(" HTML report: %s", html_file)
248314
else:
249-
logger.warning(f" ⚠️ HTML Report expected but not found: {html_file}")
315+
logger.warning(" HTML report expected but not found: %s", html_file)
250316

251317
logger.info("=" * 60)
252318

253319
if failed > 0:
254-
logger.warning(f"⚠️ {failed} test(s) failed. Check reports for details.")
320+
logger.warning("%s test(s) failed. Check reports for details.", failed)
255321

256322
failed_tests = terminalreporter.stats.get("failed", [])
257323
for test_report in failed_tests[:5]:
@@ -262,13 +328,13 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
262328
if test_report.longrepr
263329
else "Unknown error"
264330
)
265-
logger.error(f" {test_name}: {error_msg}")
331+
logger.error(" %s: %s", test_name, error_msg)
266332

267333
if len(failed_tests) > 5:
268334
logger.error(f" ... and {len(failed_tests) - 5} more failures")
269335

270336
if errors > 0:
271-
logger.error(f"💥 {errors} test(s) had errors. Check reports for details.")
337+
logger.error("%s test(s) had errors. Check reports for details.", errors)
272338

273339
error_tests = terminalreporter.stats.get("error", [])
274340
for test_report in error_tests[:3]:
@@ -279,12 +345,17 @@ def pytest_terminal_summary(terminalreporter, exitstatus, config):
279345
if test_report.longrepr
280346
else "Unknown error"
281347
)
282-
logger.error(f" 💥 {test_name}: {error_msg}")
348+
logger.error(" %s: %s", test_name, error_msg)
283349

284350
if len(error_tests) > 3:
285351
logger.error(f" ... and {len(error_tests) - 3} more errors")
286352

287353
if passed == total and total > 0:
288-
logger.info("🎉 All tests passed successfully!")
354+
logger.info("All tests passed successfully!")
289355

290356
logger.info("=" * 60)
357+
358+
if _saved_failure_logs:
359+
logger.info("Failure log artifacts:")
360+
for path in _saved_failure_logs:
361+
logger.info(" %s", path)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import Type
2+
3+
from ..environment import ConfigurationError, EnvironmentConfig
4+
from .base import Provider, SessionMetadata
5+
from .browserstack import BrowserStackProvider
6+
from .local import LocalProvider
7+
8+
_PROVIDER_REGISTRY = {
9+
"local": LocalProvider,
10+
"browserstack": BrowserStackProvider,
11+
}
12+
13+
14+
def create_provider(env_config: EnvironmentConfig) -> Provider:
15+
provider_name = env_config.provider.name.lower()
16+
try:
17+
provider_cls: Type[Provider] = _PROVIDER_REGISTRY[provider_name]
18+
except KeyError as exc:
19+
raise ConfigurationError(
20+
f"Provider '{env_config.provider.name}' is not registered"
21+
) from exc
22+
return provider_cls(env_config)
23+
24+
25+
__all__ = ["Provider", "SessionMetadata", "create_provider"]

0 commit comments

Comments
 (0)