Skip to content
Merged
48 changes: 46 additions & 2 deletions launchable/test_runners/playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
# https://playwright.dev/
#
import json
from pathlib import Path
from typing import Dict, Generator, List

import click
from junitparser import TestCase, TestSuite # type: ignore

from ..commands.record.case_event import CaseEvent
from ..testpath import TestPath
from ..testpath import TestPath, prepend_path_if_missing, relative_subpath
from . import launchable

TEST_CASE_DELIMITER = " › "
Expand Down Expand Up @@ -173,13 +174,56 @@ def parse_func(self, report_file: str) -> Generator[CaseEvent, None, None]:
click.echo("Can't find test results from {}. Make sure to confirm report file.".format(
report_file), err=True)

root_dir_relpath = self._compute_root_dir_relpath(data)
config_dir = self._config_dir(data)
for s in suites:
# The title of the root suite object contains the file name.
test_file = str(s.get("title", ""))
test_file = self._resolve_test_file(str(s.get("title", "")), root_dir_relpath, config_dir)

for event in self._parse_suites(test_file, s, []):
yield event

def _compute_root_dir_relpath(self, report: Dict) -> str:
"""
Playwright JSON stores test `file` paths relative to `config.rootDir`.
Our CLI wants paths relative to the Playwright config directory
(usually the project/repo root), so we compute:
relpath(root_dir, base_dir)
where base_dir = dirname(configFile).

Example:
configFile = /repo/playwright.config.ts
rootDir = /repo/tests
relpath(...) -> "tests"
"""
config: Dict = report.get("config", {})
config_dir = self._config_dir(report)
root_dir = str(config.get("rootDir", ""))
# TODO: We currently haven't supported the following sibling/parent path cases.
# configFile = /repo/foo/playwright.config.ts
# rootDir = /repo/tests
if config_dir and root_dir:
return relative_subpath(root_dir, config_dir)

return ""

def _config_dir(self, report: Dict) -> str:
config: Dict = report.get("config", {})
config_file = str(config.get("configFile", ""))
if config_file:
return Path(config_file).parent.as_posix()

return ""

def _resolve_test_file(self, test_file: str, root_dir_relpath: str, config_dir: str) -> str:
test_file = prepend_path_if_missing(test_file, root_dir_relpath)
if self.client.base_path and config_dir:
# When --base is set, hand an absolute path to make_file_path_component()
# so its existing base_path relativization works as intended.
return Path(config_dir, test_file).absolute().as_posix()

return test_file

def _parse_suites(self, test_file: str, suite: Dict[str, Dict], test_case_names: List[str] = []) -> List:
events = []

Expand Down
25 changes: 25 additions & 0 deletions launchable/testpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ def _relative_to(p: pathlib.Path, base: str) -> pathlib.Path:
return resolved.relative_to(base)


def relative_subpath(path: str, base_path: str) -> str:
if not path or not base_path:
return ""

try:
relpath = pathlib.Path(path).relative_to(pathlib.Path(base_path)).as_posix()
except ValueError:
return ""

if relpath == ".":
return ""

return relpath


def prepend_path_if_missing(path: str, prefix: str) -> str:
if not path or not prefix:
return path

if path.startswith(prefix):
return path

return pathlib.Path(prefix, path).as_posix()


class FilePathNormalizer:
"""Normalize file paths based on the Git repository root

Expand Down
Empty file.
1 change: 1 addition & 0 deletions tests/data/playwright/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

49 changes: 49 additions & 0 deletions tests/data/playwright/record_test_result_with_json_base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"events": [
{
"type": "case",
"testPath": [
{
"type": "file",
"name": "tests/a.spec.ts"
},
{
"type": "testcase",
"name": "smoke \u203a passes"
}
],
"duration": 0.012,
"status": 1,
"stdout": "",
"stderr": "",
"data": {
"lineNumber": 10
}
},
{
"type": "case",
"testPath": [
{
"type": "file",
"name": "tests/b.spec.ts"
},
{
"type": "testcase",
"name": "smoke \u203a already prefixed"
}
],
"duration": 0.015,
"status": 1,
"stdout": "",
"stderr": "",
"data": {
"lineNumber": 20
}
}
],
"testRunner": "playwright",
"group": "",
"noBuild": false,
"flavors": [],
"testSuite": ""
}
49 changes: 49 additions & 0 deletions tests/data/playwright/record_test_result_with_prefix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"events": [
{
"type": "case",
"testPath": [
{
"type": "file",
"name": "packages/e2e/tests/a.spec.ts"
},
{
"type": "testcase",
"name": "smoke \u203a passes"
}
],
"duration": 0.012,
"status": 1,
"stdout": "",
"stderr": "",
"data": {
"lineNumber": 10
}
},
{
"type": "case",
"testPath": [
{
"type": "file",
"name": "packages/e2e/tests/b.spec.ts"
},
{
"type": "testcase",
"name": "smoke \u203a already prefixed"
}
],
"duration": 0.015,
"status": 1,
"stdout": "",
"stderr": "",
"data": {
"lineNumber": 20
}
}
],
"testRunner": "playwright",
"group": "",
"noBuild": false,
"flavors": [],
"testSuite": ""
}
64 changes: 64 additions & 0 deletions tests/data/playwright/report_with_json_base.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"config": {
"configFile": "tests/data/playwright/playwright.config.ts",
"rootDir": "tests/data/playwright/packages/e2e"
},
"suites": [
{
"title": "tests/a.spec.ts",
"specs": [],
"suites": [
{
"title": "smoke",
"specs": [
{
"title": "passes",
"line": 10,
"tests": [
{
"results": [
{
"status": "passed",
"duration": 12,
"stdout": [],
"errors": []
}
]
}
]
}
],
"suites": []
}
]
},
{
"title": "packages/e2e/tests/b.spec.ts",
"specs": [],
"suites": [
{
"title": "smoke",
"specs": [
{
"title": "already prefixed",
"line": 20,
"tests": [
{
"results": [
{
"status": "passed",
"duration": 15,
"stdout": [],
"errors": []
}
]
}
]
}
],
"suites": []
}
]
}
]
}
64 changes: 64 additions & 0 deletions tests/data/playwright/report_with_prefix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"config": {
"configFile": "/repo/playwright.config.ts",
"rootDir": "/repo/packages/e2e"
},
"suites": [
{
"title": "tests/a.spec.ts",
"specs": [],
"suites": [
{
"title": "smoke",
"specs": [
{
"title": "passes",
"line": 10,
"tests": [
{
"results": [
{
"status": "passed",
"duration": 12,
"stdout": [],
"errors": []
}
]
}
]
}
],
"suites": []
}
]
},
{
"title": "packages/e2e/tests/b.spec.ts",
"specs": [],
"suites": [
{
"title": "smoke",
"specs": [
{
"title": "already prefixed",
"line": 20,
"tests": [
{
"results": [
{
"status": "passed",
"duration": 15,
"stdout": [],
"errors": []
}
]
}
]
}
],
"suites": []
}
]
}
]
}
28 changes: 28 additions & 0 deletions tests/test_runners/test_playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import sys
import unittest
from pathlib import Path
from unittest import mock

import responses # type: ignore
Expand Down Expand Up @@ -64,3 +65,30 @@ def _test_test_path_status(payload, test_path: str, status: CaseEvent) -> bool:
'playwright', '--json', str(self.test_files_dir.joinpath("report.json")))
json_payload = json.loads(gzip.decompress(self.find_request('/events', 1).request.body).decode())
self.assertEqual(_test_test_path_status(json_payload, target_test_path, CaseEvent.TEST_FAILED), True)

@responses.activate
@mock.patch.dict(os.environ,
{"LAUNCHABLE_TOKEN": CliTestCase.launchable_token})
def test_record_test_with_json_option_adds_prefix_from_config(self):
report_file = str(self.test_files_dir.joinpath("report_with_prefix.json"))

result = self.cli('record', 'tests', '--session', self.session,
'playwright', '--json', report_file)

self.assert_success(result)
self.assert_record_tests_payload('record_test_result_with_prefix.json')

@responses.activate
@mock.patch.dict(os.environ,
{"LAUNCHABLE_TOKEN": CliTestCase.launchable_token})
@unittest.skipIf(
sys.platform.startswith("win"),
"The base path fixture uses POSIX-style paths"
)
def test_record_test_with_json_option_respects_base_path(self):
result = self.cli('record', 'tests', '--session', self.session,
'--base', str(self.test_files_dir.joinpath("packages", "e2e")),
'playwright', '--json', str(self.test_files_dir.joinpath("report_with_json_base.json")))

self.assert_success(result)
self.assert_record_tests_payload('record_test_result_with_json_base.json')
Loading
Loading