Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 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,35 @@ 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)
for s in suites:
# The title of the root suite object contains the file name.
test_file = str(s.get("title", ""))
test_file = prepend_path_if_missing(str(s.get("title", "")), root_dir_relpath)

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_file = str(config.get("configFile", ""))
root_dir = str(config.get("rootDir", ""))
if not config_file or not root_dir:
return ""

return relative_subpath(root_dir, str(Path(config_file).parent))

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
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": []
}
]
}
]
}
16 changes: 16 additions & 0 deletions tests/test_runners/test_playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,19 @@ 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)

payload = json.loads(gzip.decompress(self.find_request('/events').request.body).decode())
test_paths = [unparse_test_path(event.get("testPath")) for event in payload.get("events")]
self.assertIn("file=packages/e2e/tests/a.spec.ts#testcase=smoke › passes", test_paths)
self.assertIn("file=packages/e2e/tests/b.spec.ts#testcase=smoke › already prefixed", test_paths)
34 changes: 33 additions & 1 deletion tests/test_testpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import sys
import tempfile
import unittest

# hello smart tests
from launchable.testpath import FilePathNormalizer, parse_test_path, unparse_test_path
from launchable.testpath import FilePathNormalizer, parse_test_path, prepend_path_if_missing, relative_subpath, unparse_test_path


class TestPathEncodingTest(unittest.TestCase):
Expand Down Expand Up @@ -136,5 +137,36 @@ def _run_command(self, args, cwd=None):
self.fail("Failed to execute a command: {}\nSTDOUT: {}\nSTDERR: {}\n". format(e, e.stdout, e.stderr))


class TestPathHelpers(unittest.TestCase):
def test_relative_subpath(self):
self.assertEqual(
"tests",
relative_subpath(str(pathlib.Path("repo", "tests")), str(pathlib.Path("repo"))))

def test_relative_subpath_returns_empty_when_same_path(self):
self.assertEqual(
"",
relative_subpath(str(pathlib.Path("repo")), str(pathlib.Path("repo"))))

def test_relative_subpath_returns_empty_when_not_under_base(self):
self.assertEqual(
"",
relative_subpath(str(pathlib.Path("repo", "tests")), str(pathlib.Path("other"))))

def test_prepend_path_if_missing(self):
self.assertEqual(
"tests/a.spec.ts",
prepend_path_if_missing("a.spec.ts", "tests"))

def test_prepend_path_if_missing_when_already_prefixed(self):
self.assertEqual(
"tests/a.spec.ts",
prepend_path_if_missing("tests/a.spec.ts", "tests"))

def test_prepend_path_if_missing_when_empty_input(self):
self.assertEqual("", prepend_path_if_missing("", "tests"))
self.assertEqual("a.spec.ts", prepend_path_if_missing("a.spec.ts", ""))


if __name__ == '__main__':
unittest.main()
Loading