From 21bea35828cba33201437358b9b139f664b69e91 Mon Sep 17 00:00:00 2001 From: gayanW Date: Wed, 26 Nov 2025 08:54:16 +0900 Subject: [PATCH 1/2] Add subsetting to Karma runner --- launchable/test_runners/karma.py | 39 +++-- tests/data/karma/README.md | 155 +++++++++++++++++++ tests/data/{ng => karma}/subset_payload.json | 6 +- tests/test_runners/test_karma.py | 25 ++- tests/test_runners/test_ng.py | 22 --- 5 files changed, 202 insertions(+), 45 deletions(-) create mode 100644 tests/data/karma/README.md rename tests/data/{ng => karma}/subset_payload.json (73%) delete mode 100644 tests/test_runners/test_ng.py diff --git a/launchable/test_runners/karma.py b/launchable/test_runners/karma.py index 12ed811eb..ca07bbede 100644 --- a/launchable/test_runners/karma.py +++ b/launchable/test_runners/karma.py @@ -1,6 +1,3 @@ -# This runner only supports recording tests -# For subsetting, use 'ng' test runner instead -# It's possible to use 'karma' runner for recording, and 'ng' runner for subsetting, for the same test session import json from typing import Dict, Generator, List @@ -11,18 +8,6 @@ from . import launchable -@click.option('--with', '_with') -@launchable.subset -def subset(client, _with: str): - # TODO: implement the --with ng option - - # read lines as test file names - for t in client.stdin(): - client.test_path(t.rstrip("\n")) - - client.run() - - @click.argument('reports', required=True, nargs=-1) @launchable.record.tests def record_tests(client, reports): @@ -34,6 +19,30 @@ def record_tests(client, reports): client.run() +@click.option('--with', '_with', type=str, default=None, + help='Format output for specific test runner (e.g., "ng" for Angular CLI)') +@launchable.subset +def subset(client, _with): + """ + Usage: + find src -name "*.spec.ts" -o -name "*.spec.js" > test-list.txt + cat test-list.txt | launchable subset --target 10% karma + + # Output in ng test format + find src -name "*.spec.ts" | launchable subset --target 10% karma --with ng + """ + for t in client.stdin(): + path = t.strip() + if path: + client.test_path(path) + + if _with == 'ng': + client.formatter = lambda x: "--include={}".format(x[0]['name']) + client.separator = " " + + client.run() + + class JSONReportParser: """ Sample Karma report format: diff --git a/tests/data/karma/README.md b/tests/data/karma/README.md new file mode 100644 index 000000000..7c2039115 --- /dev/null +++ b/tests/data/karma/README.md @@ -0,0 +1,155 @@ +Karma +====== + +### Karma Project + +**Create project:** + +```bash +npm init -y +npm install --save-dev karma karma-jasmine jasmine-core karma-chrome-launcher karma-json-reporter +``` +The instructions are based on: +https://karma-runner.github.io/6.4/intro/installation.html + +**Generate `karma.conf.js`:** +```bash +npx karma init + +# Answer the prompts: +# - Framework: jasmine +# - Require.js: no +# - Browser: ChromeHeadless +# - Test files: +# - Files to exclude: +# - Watch files: no +``` + +**Add following to `karma.conf.js` while keeping the current settings:** +``` +module.exports = function (config) { + config.set({ + files: process.env.KARMA_FILES ? JSON.parse(process.env.KARMA_FILES) : [], + ... + plugins: [ + ... + require('karma-json-reporter') + ], + jsonReporter: { + outputFile: require('path').join(__dirname, 'test-results.json'), + stdout: false + }, + reporters: [..., 'json'] + }); +}; +``` + +**Create a test file:** +```bash +mkdir test + +cat > test/example.spec.js << 'EOF' +describe('Example', function() { + it('should pass', function() { + expect(true).toBe(true); + }); + + it('should add numbers', function() { + expect(1 + 1).toBe(2); + }); +}); +EOF +``` + +**Record session:** +```bash +git add . && git commit -m "Initial commit" +launchable record build --name ${BUILD_NAME} +launchable record session --build ${BUILD_NAME} > session.txt +``` + +**Run all tests:** +```bash +find test -name "*.spec.ts" -o -name "*.spec.js" > test_list.txt +cat test_list.txt +KARMA_FILES=$(cat test_list.txt | jq -R -s -c 'split("\n")[:-1]') +npx karma start --single-run +``` + +**Record tests:** +```bash +launchable record tests karma test-results.json +``` + +**Request subset:** +```bash +cat test_list.txt | launchable subset --target 25% karma > subset.txt +``` + +**Run subset of tests:** +```bash +KARMA_FILES=$(cat subset.txt | jq -R -s -c 'split("\n")[:-1]') +npx karma start --single-run +``` + +### Angular Project with Karma + +**Create project:** + +``` +ng new ng-karma-app --test-runner=karma +cd ng-karma-app +npm install --save-dev karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core karma-json-reporter @types/jasmine +ng test --no-watch --no-progress --browsers=ChromeHeadless +``` + +The instructions are based on: +- https://angular.dev/guide/testing/karma +- https://www.npmjs.com/package/karma-json-reporter + +**Generate `karma.conf.js`:** +``` +ng generate config karma +``` + +**Add following to `karma.conf.js` while keeping the current settings:** +``` +module.exports = function (config) { + config.set({ + ... + plugins: [ + ... + require('karma-json-reporter') + ], + jsonReporter: { + outputFile: require('path').join(__dirname, 'test-results.json'), + stdout: false + }, + reporters: [..., 'json'] + }); +}; +``` + +**Record session:** +``` +git add . && git commit -m "Initial commit" +launchable record build --name ${BUILD_NAME} +launchable record session --build ${BUILD_NAME} > session.txt +``` + +**Record tests:** +``` +ng test --no-watch --no-progress --browsers=ChromeHeadless +launchable record tests karma test-results.json +``` + +**Subset tests with **ng**:** +``` +ng test --list-tests | grep src > test_list.txt +cat test_list.txt | launchable subset --target 25% karma --with ng > subset.txt +``` + +**Run subset of tests** +``` +ng test --no-watch --no-progress --browsers=ChromeHeadless --include $(cat subset.txt) +``` diff --git a/tests/data/ng/subset_payload.json b/tests/data/karma/subset_payload.json similarity index 73% rename from tests/data/ng/subset_payload.json rename to tests/data/karma/subset_payload.json index d0b15b816..3d10b6085 100644 --- a/tests/data/ng/subset_payload.json +++ b/tests/data/karma/subset_payload.json @@ -4,13 +4,13 @@ { "type": "file", "name": "foo/bar/zot.spec.ts" } ], [ - { "type": "file", "name": "client-source/src/app/shared/other-test.spec.ts" } + { "type": "file", "name": "foo/bar/another.spec.ts" } ] ], - "testRunner": "ng", + "testRunner": "karma", "goal": {"type": "subset-by-percentage", "percentage": 0.1}, "ignoreNewTests": false, "session": { "id": "16" }, "getTestsFromGuess": false, "getTestsFromPreviousSessions": false -} +} \ No newline at end of file diff --git a/tests/test_runners/test_karma.py b/tests/test_runners/test_karma.py index c8163cb86..8f9cc825c 100644 --- a/tests/test_runners/test_karma.py +++ b/tests/test_runners/test_karma.py @@ -19,12 +19,27 @@ def test_record_tests_json(self): self.assert_record_tests_payload('record_test_result.json') @responses.activate - @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) + @mock.patch.dict(os.environ, + {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) def test_subset(self): - # emulate launchable record build write_build(self.build_name) - result = self.cli('subset', '--target', '10%', '--base', - os.getcwd(), 'karma', '--with', 'ng', input="a.ts\nb.ts") + subset_input = """foo/bar/zot.spec.ts +foo/bar/another.spec.ts +""" + result = self.cli('subset', '--target', '10%', 'karma', input=subset_input) + self.assert_success(result) + self.assert_subset_payload('subset_payload.json') + + @responses.activate + @mock.patch.dict(os.environ, + {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) + def test_subset_with_ng(self): + write_build(self.build_name) + + subset_input = """foo/bar/zot.spec.ts +foo/bar/another.spec.ts +""" + result = self.cli('subset', '--target', '10%', 'karma', '--with', 'ng', input=subset_input) self.assert_success(result) - self.assert_subset_payload('subset_result.json') + self.assert_subset_payload('subset_payload.json') diff --git a/tests/test_runners/test_ng.py b/tests/test_runners/test_ng.py deleted file mode 100644 index 36c090c29..000000000 --- a/tests/test_runners/test_ng.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -from unittest import mock - -import responses # type: ignore - -from launchable.utils.session import write_build -from tests.cli_test_case import CliTestCase - - -class NgTest(CliTestCase): - @responses.activate - @mock.patch.dict(os.environ, - {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) - def test_subset(self): - write_build(self.build_name) - - subset_input = """foo/bar/zot.spec.ts -client-source/src/app/shared/other-test.spec.ts -""" - result = self.cli('subset', '--target', '10%', 'ng', input=subset_input) - self.assert_success(result) - self.assert_subset_payload('subset_payload.json') From 7af5fcf95859191141cd6d9f7b2cd1be6be44295 Mon Sep 17 00:00:00 2001 From: gayanW Date: Wed, 26 Nov 2025 09:49:00 +0900 Subject: [PATCH 2/2] Restore deleted test in test_karma.py --- tests/test_runners/test_karma.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_runners/test_karma.py b/tests/test_runners/test_karma.py index 8f9cc825c..375561dcd 100644 --- a/tests/test_runners/test_karma.py +++ b/tests/test_runners/test_karma.py @@ -18,6 +18,17 @@ def test_record_tests_json(self): self.assert_success(result) self.assert_record_tests_payload('record_test_result.json') + @responses.activate + @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token}) + def test_subset_with_base(self): + # emulate launchable record build + write_build(self.build_name) + + result = self.cli('subset', '--target', '10%', '--base', + os.getcwd(), 'karma', '--with', 'ng', input="a.ts\nb.ts") + self.assert_success(result) + self.assert_subset_payload('subset_result.json') + @responses.activate @mock.patch.dict(os.environ, {"LAUNCHABLE_TOKEN": CliTestCase.launchable_token})