diff --git a/CHANGELOG.md b/CHANGELOG.md
index da3f011..7a47e68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+### Added
+- The Playwright's tags are parsed to the RP's attributes and attached to the tests.
## [5.1.6] - 2023-12-19
### Fixed
diff --git a/README.md b/README.md
index dc9f1ac..d9f034a 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,14 @@
# @reportportal/agent-js-playwright
Agent to integrate Playwright with ReportPortal.
-* More about [Playwright](https://playwright.dev/)
-* More about [ReportPortal](http://reportportal.io/)
+
+- More about [Playwright](https://playwright.dev/)
+- More about [ReportPortal](http://reportportal.io/)
## Installation
+
Install the agent in your project:
+
```cmd
npm install --save-dev @reportportal/agent-js-playwright
```
@@ -13,37 +16,38 @@ npm install --save-dev @reportportal/agent-js-playwright
## Configuration
**1.** Create `playwright.config.ts` or `*.config.js` file with reportportal configuration:
-```typescript
- import { PlaywrightTestConfig } from '@playwright/test';
-
- const RPconfig = {
- apiKey: '00000000-0000-0000-0000-000000000000',
- endpoint: 'https://your.reportportal.server/api/v1',
- project: 'Your reportportal project name',
- launch: 'Your launch name',
- attributes: [
- {
- key: 'key',
- value: 'value',
- },
- {
- value: 'value',
- },
- ],
- description: 'Your launch description',
- };
- const config: PlaywrightTestConfig = {
- reporter: [['@reportportal/agent-js-playwright', RPconfig]],
- testDir: './tests',
- };
- export default config;
+```typescript
+import { PlaywrightTestConfig } from '@playwright/test';
+
+const RPconfig = {
+ apiKey: '00000000-0000-0000-0000-000000000000',
+ endpoint: 'https://your.reportportal.server/api/v1',
+ project: 'Your reportportal project name',
+ launch: 'Your launch name',
+ attributes: [
+ {
+ key: 'key',
+ value: 'value',
+ },
+ {
+ value: 'value',
+ },
+ ],
+ description: 'Your launch description',
+};
+
+const config: PlaywrightTestConfig = {
+ reporter: [['@reportportal/agent-js-playwright', RPconfig]],
+ testDir: './tests',
+};
+export default config;
```
The full list of available options presented below.
| Option | Necessity | Default | Description |
-|---------------------------------------------|------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| ------------------------------------------- | ---------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| apiKey | Required | | User's reportportal token from which you want to send requests. It can be found on the profile page of this user. |
| endpoint | Required | | URL of your server. For example 'https://server:8080/api/v1'. |
| launch | Required | | Name of launch at creation. |
@@ -52,27 +56,28 @@ The full list of available options presented below.
| description | Optional | '' | Launch description. |
| rerun | Optional | false | Enable [rerun](https://reportportal.io/docs/dev-guides/RerunDevelopersGuide) |
| rerunOf | Optional | Not set | UUID of launch you want to rerun. If not specified, reportportal will update the latest launch with the same name |
-| mode | Optional | 'DEFAULT' | Results will be submitted to Launches page
*'DEBUG'* - Results will be submitted to Debug page. |
-| skippedIssue | Optional | true | reportportal provides feature to mark skipped tests as not 'To Investigate'.
Option could be equal boolean values:
*true* - skipped tests considered as issues and will be marked as 'To Investigate' on reportportal.
*false* - skipped tests will not be marked as 'To Investigate' on application. |
+| mode | Optional | 'DEFAULT' | Results will be submitted to Launches page
_'DEBUG'_ - Results will be submitted to Debug page. |
+| skippedIssue | Optional | true | reportportal provides feature to mark skipped tests as not 'To Investigate'.
Option could be equal boolean values:
_true_ - skipped tests considered as issues and will be marked as 'To Investigate' on reportportal.
_false_ - skipped tests will not be marked as 'To Investigate' on application. |
| debug | Optional | false | This flag allows seeing the logs of the client-javascript. Useful for debugging. |
-| launchId | Optional | Not set | The _ID_ of an already existing launch. The launch must be in 'IN_PROGRESS' status while the tests are running. Please note that if this _ID_ is provided, the launch will not be finished at the end of the run and must be finished separately. |
+| launchId | Optional | Not set | The _ID_ of an already existing launch. The launch must be in 'IN*PROGRESS' status while the tests are running. Please note that if this \_ID* is provided, the launch will not be finished at the end of the run and must be finished separately. |
| restClientConfig | Optional | Not set | The object with `agent` property for configure [http(s)](https://nodejs.org/api/https.html#https_https_request_url_options_callback) client, may contain other client options eg. [`timeout`](https://github.com/reportportal/client-javascript#timeout-30000ms-on-axios-requests).
Visit [client-javascript](https://github.com/reportportal/client-javascript) for more details. |
| launchUuidPrint | Optional | false | Whether to print the current launch UUID. |
| launchUuidPrintOutput | Optional | 'STDOUT' | Launch UUID printing output. Possible values: 'STDOUT', 'STDERR'. Works only if `launchUuidPrint` set to `true`. |
| includeTestSteps | Optional | false | Allows you to see the test steps at the log level. |
| includePlaywrightProjectNameToCodeReference | Optional | false | Includes Playwright project name to code reference. See [`testCaseId and codeRef calculation`](#setTestCaseId). It may be useful when you want to see the different history for the same test cases within different playwright projects. |
-| extendTestDescriptionWithLastError | Optional | true | If set to `true` the latest error log will be attached to the test case description. |
+| extendTestDescriptionWithLastError | Optional | true | If set to `true` the latest error log will be attached to the test case description. |
| uploadVideo | Optional | true | Whether to attach the Playwright's [video](https://playwright.dev/docs/api/class-testoptions#test-options-video) to the test case. |
| uploadTrace | Optional | true | Whether to attach the Playwright's [trace](https://playwright.dev/docs/api/class-testoptions#test-options-trace) to the test case. |
| token | Deprecated | Not set | Use `apiKey` instead. |
The following options can be overridden using ENVIRONMENT variables:
-| Option | ENV variable |
-|-------------|-----------------|
-| launchId | RP_LAUNCH_ID |
+| Option | ENV variable |
+| -------- | ------------ |
+| launchId | RP_LAUNCH_ID |
**2.** Add script to `package.json` file:
+
```json
{
"scripts": {
@@ -87,6 +92,26 @@ When organizing tests, specify titles for `test.describe` blocks, as this is nec
It is also required to specify playwright project names in `playwright.config.ts` when running the same tests in different playwright projects.
+### Advanced Test Filtering with grep Options
+
+The `@reportportal/agent-js-playwright` integration recognizes `grep` and `grepInvert` from Playwright for test filtering. These options, when used, are automatically attached as launch attributes in ReportPortal for targeted test execution.
+
+Refer to Playwright documentation for [`grep`](https://playwright.dev/docs/api/class-testconfig#test-config-grep) and [`grepInvert`](https://playwright.dev/docs/api/class-testconfig#test-config-grep-invert) to learn about their usage.
+
+```
+**Example usage in `playwright.config.ts`:**
+
+```javascript
+const config: PlaywrightTestConfig = {
+ grep: '@fast', // Only run tests tagged with @fast
+ grepInvert: '@slow', // Run tests except those tagged with @slow
+ reporter: [['@reportportal/agent-js-playwright', RPconfig]],
+ testDir: './tests',
+};
+export default config;
+```
+````
+
### Attachments
Attachments can be easily added during test run via `testInfo.attach` according to the Playwright [docs](https://playwright.dev/docs/api/class-testinfo#test-info-attach).
@@ -103,11 +128,11 @@ test('basic test', async ({ page }, testInfo) => {
});
```
-*Note:* attachment path can be provided instead of body.
+_Note:_ attachment path can be provided instead of body.
As an alternative to this approach the [`ReportingAPI`](#log) methods can be used.
-*Note:* [`ReportingAPI`](#log) methods will send attachments to ReportPortal right after their call, unlike attachments provided via `testInfo.attach` that will be reported only on the test item finish.
+_Note:_ [`ReportingAPI`](#log) methods will send attachments to ReportPortal right after their call, unlike attachments provided via `testInfo.attach` that will be reported only on the test item finish.
### Logging
@@ -132,6 +157,7 @@ As an alternative to this approach the [`ReportingAPI`](#log) methods can be use
This reporter provides Reporting API to use it directly in tests to send some additional data to the report.
To start using the `ReportingApi` in tests, just import it from `'@reportportal/agent-js-playwright'`:
+
```javascript
import { ReportingApi } from '@reportportal/agent-js-playwright';
```
@@ -143,11 +169,13 @@ All ReportingApi methods have an optional _suite_ parameter.
If you want to add a data to the suite, you must pass the suite name as the last parameter.
##### addAttributes
+
Add attributes (tags) to the current test. Should be called inside of corresponding test.
`ReportingApi.addAttributes(attributes: Array, suite?: string);`
**required**: `attributes`
**optional**: `suite`
Example:
+
```javascript
test('should have the correct attributes', () => {
ReportingApi.addAttributes([
@@ -163,13 +191,25 @@ test('should have the correct attributes', () => {
});
```
+You can now add test attributes in test titles using `@tag` notation. This provides a succinct way to include metadata in test reports. Any `@tag` in a playwright test title will be used as an attribute in ReportPortal, but won't appear in the final test title on ReportPortal.
+
+Example:
+
+```javascript
+test('@tag should have a tag at the beginning of the test title', () => {
+ expect(true).toBe(true);
+});
+```
+
##### setTestCaseId
+
Set test case id to the current test ([About test case id](https://reportportal.io/docs/Test-case-ID%3Ewhat-is-it-test-case-id)). Should be called inside of corresponding test.
`ReportingApi.setTestCaseId(id: string, suite?: string);`
**required**: `id`
**optional**: `suite`
If `testCaseId` not specified, it will be generated automatically based on [codeRef](https://reportportal.io/docs/Test-case-ID%3Ewhat-does-happen-if-you-do-not-report-items-with-test-case-id-).
Example:
+
```javascript
test('should have the correct testCaseId', () => {
ReportingApi.setTestCaseId('itemTestCaseId');
@@ -178,14 +218,16 @@ test('should have the correct testCaseId', () => {
```
##### log
+
Send logs to report portal for the current test. Should be called inside of corresponding test.
`ReportingApi.log(level: LOG_LEVELS, message: string, file?: Attachment, suite?: string);`
**required**: `level`, `message`
**optional**: `file`, `suite`
-where `level` can be one of the following: *TRACE*, *DEBUG*, *WARN*, *INFO*, *ERROR*, *FATAL*
+where `level` can be one of the following: _TRACE_, _DEBUG_, _WARN_, _INFO_, _ERROR_, _FATAL_
Example:
+
```javascript
-test('should contain logs with attachments',() => {
+test('should contain logs with attachments', () => {
const fileName = 'test.jpg';
const fileContent = fs.readFileSync(path.resolve(__dirname, './attachments', fileName));
const attachment = {
@@ -200,6 +242,7 @@ test('should contain logs with attachments',() => {
```
##### info, debug, warn, error, trace, fatal
+
Send logs with corresponding level to report portal for the current test. Should be called inside of corresponding test.
`ReportingApi.info(message: string, file?: Attachment, suite?: string);`
`ReportingApi.debug(message: string, file?: Attachment, suite?: string);`
@@ -210,26 +253,29 @@ Send logs with corresponding level to report portal for the current test. Should
**required**: `message`
**optional**: `file`, `suite`
Example:
+
```javascript
test('should contain logs with attachments', () => {
- ReportingApi.info('Log message');
- ReportingApi.debug('Log message');
- ReportingApi.warn('Log message');
- ReportingApi.error('Log message');
- ReportingApi.trace('Log message');
- ReportingApi.fatal('Log message');
-
- expect(true).toBe(true);
+ ReportingApi.info('Log message');
+ ReportingApi.debug('Log message');
+ ReportingApi.warn('Log message');
+ ReportingApi.error('Log message');
+ ReportingApi.trace('Log message');
+ ReportingApi.fatal('Log message');
+
+ expect(true).toBe(true);
});
```
##### launchLog
+
Send logs to report portal for the current launch. Should be called inside of the any test or suite.
`ReportingApi.launchLog(level: LOG_LEVELS, message: string, file?: Attachment);`
**required**: `level`, `message`
**optional**: `file`
-where `level` can be one of the following: *TRACE*, *DEBUG*, *WARN*, *INFO*, *ERROR*, *FATAL*
+where `level` can be one of the following: _TRACE_, _DEBUG_, _WARN_, _INFO_, _ERROR_, _FATAL_
Example:
+
```javascript
test('should contain logs with attachments', async () => {
const fileName = 'test.jpg';
@@ -246,6 +292,7 @@ test('should contain logs with attachments', async () => {
```
##### launchInfo, launchDebug, launchWarn, launchError, launchTrace, launchFatal
+
Send logs with corresponding level to report portal for the current launch. Should be called inside of the any test or suite.
`ReportingApi.launchInfo(message: string, file?: Attachment);`
`ReportingApi.launchDebug(message: string, file?: Attachment);`
@@ -256,35 +303,39 @@ Send logs with corresponding level to report portal for the current launch. Shou
**required**: `message`
**optional**: `file`
Example:
+
```javascript
test('should contain logs with attachments', () => {
- ReportingApi.launchInfo('Log message');
- ReportingApi.launchDebug('Log message');
- ReportingApi.launchWarn('Log message');
- ReportingApi.launchError('Log message');
- ReportingApi.launchTrace('Log message');
- ReportingApi.launchFatal('Log message');
-
- expect(true).toBe(true);
+ ReportingApi.launchInfo('Log message');
+ ReportingApi.launchDebug('Log message');
+ ReportingApi.launchWarn('Log message');
+ ReportingApi.launchError('Log message');
+ ReportingApi.launchTrace('Log message');
+ ReportingApi.launchFatal('Log message');
+
+ expect(true).toBe(true);
});
```
##### setStatus
+
Assign corresponding status to the current test item. Should be called inside of corresponding test.
`ReportingApi.setStatus(status: string, suite?: string);`
**required**: `status`
**optional**: `suite`
-where `status` must be one of the following: *passed*, *failed*, *stopped*, *skipped*, *interrupted*, *cancelled*
+where `status` must be one of the following: _passed_, _failed_, _stopped_, _skipped_, _interrupted_, _cancelled_
Example:
+
```javascript
test('should have status FAILED', () => {
- ReportingApi.setStatus('failed');
-
- expect(true).toBe(true);
+ ReportingApi.setStatus('failed');
+
+ expect(true).toBe(true);
});
```
##### setStatusFailed, setStatusPassed, setStatusSkipped, setStatusStopped, setStatusInterrupted, setStatusCancelled
+
Assign corresponding status to the current test item. Should be called inside of corresponding test.
`ReportingApi.setStatusFailed(suite?: string);`
`ReportingApi.setStatusPassed(suite?: string);`
@@ -294,31 +345,35 @@ Assign corresponding status to the current test item. Should be called inside of
`ReportingApi.setStatusCancelled(suite?: string);`
**optional**: `suite`
Example:
+
```javascript
test('should call ReportingApi to set statuses', () => {
- ReportingAPI.setStatusFailed();
- ReportingAPI.setStatusPassed();
- ReportingAPI.setStatusSkipped();
- ReportingAPI.setStatusStopped();
- ReportingAPI.setStatusInterrupted();
- ReportingAPI.setStatusCancelled();
+ ReportingAPI.setStatusFailed();
+ ReportingAPI.setStatusPassed();
+ ReportingAPI.setStatusSkipped();
+ ReportingAPI.setStatusStopped();
+ ReportingAPI.setStatusInterrupted();
+ ReportingAPI.setStatusCancelled();
});
```
##### setLaunchStatus
+
Assign corresponding status to the current launch. Should be called inside of the any test or suite.
`ReportingApi.setLaunchStatus(status: string);`
**required**: `status`
-where `status` must be one of the following: *passed*, *failed*, *stopped*, *skipped*, *interrupted*, *cancelled*
+where `status` must be one of the following: _passed_, _failed_, _stopped_, _skipped_, _interrupted_, _cancelled_
Example:
+
```javascript
-test('launch should have status FAILED', () => {
- ReportingApi.setLaunchStatus('failed');
- expect(true).toBe(true);
+test('launch should have status FAILED', () => {
+ ReportingApi.setLaunchStatus('failed');
+ expect(true).toBe(true);
});
```
##### setLaunchStatusFailed, setLaunchStatusPassed, setLaunchStatusSkipped, setLaunchStatusStopped, setLaunchStatusInterrupted, setLaunchStatusCancelled
+
Assign corresponding status to the current test item. Should be called inside of the any test or suite.
`ReportingApi.setLaunchStatusFailed();`
`ReportingApi.setLaunchStatusPassed();`
@@ -327,29 +382,33 @@ Assign corresponding status to the current test item. Should be called inside of
`ReportingApi.setLaunchStatusInterrupted();`
`ReportingApi.setLaunchStatusCancelled();`
Example:
+
```javascript
test('should call ReportingApi to set launch statuses', () => {
- ReportingAPI.setLaunchStatusFailed();
- ReportingAPI.setLaunchStatusPassed();
- ReportingAPI.setLaunchStatusSkipped();
- ReportingAPI.setLaunchStatusStopped();
- ReportingAPI.setLaunchStatusInterrupted();
- ReportingAPI.setLaunchStatusCancelled();
+ ReportingAPI.setLaunchStatusFailed();
+ ReportingAPI.setLaunchStatusPassed();
+ ReportingAPI.setLaunchStatusSkipped();
+ ReportingAPI.setLaunchStatusStopped();
+ ReportingAPI.setLaunchStatusInterrupted();
+ ReportingAPI.setLaunchStatusCancelled();
});
```
### Integration with Sauce Labs
-To integrate with Sauce Labs just add attributes for the test case:
+To integrate with Sauce Labs just add attributes for the test case:
```javascript
-[{
- "key": "SLID",
- "value": "# of the job in Sauce Labs"
-}, {
- "key": "SLDC",
- "value": "EU (your job region in Sauce Labs)"
-}]
+[
+ {
+ key: 'SLID',
+ value: '# of the job in Sauce Labs',
+ },
+ {
+ key: 'SLDC',
+ value: 'EU (your job region in Sauce Labs)',
+ },
+];
```
## Issues troubleshooting
@@ -360,17 +419,19 @@ There is known issue that in some cases launches not finished as expected in Rep
This may happen in case of error thrown from `before`/`beforeAll` hooks, retries enabled and `fullyParallel: false`. Associated with [#85](https://github.com/reportportal/agent-js-playwright/issues/85).
In this case as a workaround we suggest to use `.skip()` and `.fixme()` annotations inside the test body:
-use
+use
+
```javascript
- test('example fail', async ({}) => {
- test.fixme();
- expect(1).toBeGreaterThan(2);
- });
+test('example fail', async ({}) => {
+ test.fixme();
+ expect(1).toBeGreaterThan(2);
+});
```
-instead of
+instead of
+
```javascript
- test.fixme('example fail', async ({}) => {
- expect(1).toBeGreaterThan(2);
- });
+test.fixme('example fail', async ({}) => {
+ expect(1).toBeGreaterThan(2);
+});
```
diff --git a/src/__tests__/reporter/retriesReporting.spec.ts b/src/__tests__/reporter/retriesReporting.spec.ts
index 59a82ae..4c8c6be 100644
--- a/src/__tests__/reporter/retriesReporting.spec.ts
+++ b/src/__tests__/reporter/retriesReporting.spec.ts
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-import { RPReporter } from '../../reporter';
-import { mockConfig } from '../mocks/configMock';
import { RPClientMock } from '../mocks/RPClientMock';
+import { RPReporter } from '../../reporter';
import { StartTestObjType } from '../../models';
import { TEST_ITEM_TYPES } from '../../constants';
+import { mockConfig } from '../mocks/configMock';
describe('retries reporting', () => {
const reporter = new RPReporter(mockConfig);
diff --git a/src/__tests__/reporter/startSuiteTestReporting.spec.ts b/src/__tests__/reporter/startSuiteTestReporting.spec.ts
index 76d02bb..78ef9dd 100644
--- a/src/__tests__/reporter/startSuiteTestReporting.spec.ts
+++ b/src/__tests__/reporter/startSuiteTestReporting.spec.ts
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-import { RPReporter } from '../../reporter';
-import { mockConfig } from '../mocks/configMock';
import { RPClientMock } from '../mocks/RPClientMock';
+import { RPReporter } from '../../reporter';
import { StartTestObjType } from '../../models';
import { TEST_ITEM_TYPES } from '../../constants';
+import { mockConfig } from '../mocks/configMock';
import path from 'path';
const rootSuite = 'tests/example.js';
@@ -27,6 +27,48 @@ const suiteName = 'suiteName';
describe('start reporting suite/test', () => {
let reporter: RPReporter;
let spyStartTestItem: jest.SpyInstance;
+ const expectedTestObj: StartTestObjType = {
+ name: 'testTitle',
+ type: TEST_ITEM_TYPES.STEP,
+ codeRef: 'tests/example.js/suiteName/testTitle',
+ retry: false,
+ };
+
+ const expectedParentSuiteObj: StartTestObjType = {
+ name: suiteName,
+ type: TEST_ITEM_TYPES.TEST,
+ codeRef: 'tests/example.js/suiteName',
+ };
+
+ const expectedRootParentSuiteObj: StartTestObjType = {
+ name: rootSuite,
+ type: TEST_ITEM_TYPES.SUITE,
+ codeRef: 'tests/example.js',
+ };
+
+ const expectedSuites = new Map([
+ [
+ rootSuite,
+ {
+ id: 'tempTestItemId',
+ name: rootSuite,
+ testInvocationsLeft: 1,
+ descendants: ['testItemId'],
+ },
+ ],
+ [
+ `${rootSuite}/${suiteName}`,
+ {
+ id: 'tempTestItemId',
+ name: suiteName,
+ descendants: ['testItemId'],
+ testInvocationsLeft: 1,
+ },
+ ],
+ ]);
+
+ const expectedTestItems = new Map([['testItemId', { id: 'tempTestItemId', name: 'testTitle' }]]);
+ const parentId = 'tempTestItemId';
const testCase = {
title: 'testTitle',
@@ -69,6 +111,10 @@ describe('start reporting suite/test', () => {
reporter.launchId = 'tempLaunchId';
spyStartTestItem = jest.spyOn(reporter.client, 'startTestItem');
+
+ expectedTestObj.startTime = reporter.client.helpers.now();
+ expectedParentSuiteObj.startTime = reporter.client.helpers.now();
+ expectedRootParentSuiteObj.startTime = reporter.client.helpers.now();
});
afterEach(() => {
@@ -76,50 +122,6 @@ describe('start reporting suite/test', () => {
});
test('client.startTestItem should be called with corresponding params to report suites and test item', () => {
- const expectedSuites = new Map([
- [
- rootSuite,
- {
- id: 'tempTestItemId',
- name: rootSuite,
- testInvocationsLeft: 1,
- descendants: ['testItemId'],
- },
- ],
- [
- `${rootSuite}/${suiteName}`,
- {
- id: 'tempTestItemId',
- name: suiteName,
- descendants: ['testItemId'],
- testInvocationsLeft: 1,
- },
- ],
- ]);
- const expectedTestItems = new Map([
- ['testItemId', { id: 'tempTestItemId', name: 'testTitle' }],
- ]);
- const expectedRootParentSuiteObj: StartTestObjType = {
- startTime: reporter.client.helpers.now(),
- name: rootSuite,
- type: TEST_ITEM_TYPES.SUITE,
- codeRef: 'tests/example.js',
- };
- const expectedParentSuiteObj: StartTestObjType = {
- startTime: reporter.client.helpers.now(),
- name: suiteName,
- type: TEST_ITEM_TYPES.TEST,
- codeRef: 'tests/example.js/suiteName',
- };
- const expectedTestObj: StartTestObjType = {
- startTime: reporter.client.helpers.now(),
- name: 'testTitle',
- type: TEST_ITEM_TYPES.STEP,
- codeRef: 'tests/example.js/suiteName/testTitle',
- retry: false,
- };
- const parentId = 'tempTestItemId';
-
// @ts-ignore
reporter.onTestBegin(testCase);
@@ -158,4 +160,64 @@ describe('start reporting suite/test', () => {
expect(reporter.suites).toEqual(new Map());
expect(reporter.testItems).toEqual(new Map());
});
+
+ test('client.startTestItem should be called with corresponding params while one tag provided at the beginning of the test case title', () => {
+ // @ts-ignore
+ reporter.onTestBegin({ ...testCase, title: `@tag ${testCase.title}` });
+
+ // the first call for the root suite start
+ expect(spyStartTestItem).toHaveBeenNthCalledWith(
+ 1,
+ expectedRootParentSuiteObj,
+ reporter.launchId,
+ undefined,
+ );
+ // the first call for the item parent suite start
+ expect(spyStartTestItem).toHaveBeenNthCalledWith(
+ 2,
+ expectedParentSuiteObj,
+ reporter.launchId,
+ parentId,
+ );
+ // the third call for the test item start
+ expect(reporter.client.startTestItem).toHaveBeenNthCalledWith(
+ 3,
+ { ...expectedTestObj, attributes: [{ value: 'tag' }] },
+ reporter.launchId,
+ parentId,
+ );
+ expect(spyStartTestItem).toHaveBeenCalledTimes(3);
+ expect(reporter.suites).toEqual(expectedSuites);
+ expect(reporter.testItems).toEqual(expectedTestItems);
+ });
+
+ test('client.startTestItem should be called with corresponding params while one tag provided at the end of the test case title', () => {
+ // @ts-ignore
+ reporter.onTestBegin({ ...testCase, title: `${testCase.title} @tag` });
+
+ // the first call for the root suite start
+ expect(spyStartTestItem).toHaveBeenNthCalledWith(
+ 1,
+ expectedRootParentSuiteObj,
+ reporter.launchId,
+ undefined,
+ );
+ // the first call for the item parent suite start
+ expect(spyStartTestItem).toHaveBeenNthCalledWith(
+ 2,
+ expectedParentSuiteObj,
+ reporter.launchId,
+ parentId,
+ );
+ // the third call for the test item start
+ expect(reporter.client.startTestItem).toHaveBeenNthCalledWith(
+ 3,
+ { ...expectedTestObj, attributes: [{ value: 'tag' }] },
+ reporter.launchId,
+ parentId,
+ );
+ expect(spyStartTestItem).toHaveBeenCalledTimes(3);
+ expect(reporter.suites).toEqual(expectedSuites);
+ expect(reporter.testItems).toEqual(expectedTestItems);
+ });
});
diff --git a/src/reporter.ts b/src/reporter.ts
index d90ca33..f29af0b 100644
--- a/src/reporter.ts
+++ b/src/reporter.ts
@@ -17,7 +17,13 @@
import RPClient from '@reportportal/client-javascript';
import stripAnsi from 'strip-ansi';
-import { Reporter, Suite as PWSuite, TestCase, TestResult } from '@playwright/test/reporter';
+import {
+ Reporter,
+ Suite as PWSuite,
+ TestCase,
+ TestResult,
+ FullConfig,
+} from '@playwright/test/reporter';
import {
Attribute,
FinishTestItemObjType,
@@ -266,7 +272,7 @@ export class RPReporter implements Reporter {
});
}
- onBegin(): void {
+ onBegin(config?: FullConfig): void {
const { launch, description, attributes, skippedIssue, rerun, rerunOf, mode, launchId } =
this.config;
const systemAttributes: Attribute[] = getSystemAttributes(skippedIssue);
@@ -282,6 +288,16 @@ export class RPReporter implements Reporter {
mode: mode || LAUNCH_MODES.DEFAULT,
id: launchId,
};
+
+ // Extract grep and grepInvert from config and add as launch attributes
+ if (config?.grep) {
+ startLaunchObj.attributes.push({ key: 'grep', value: config.grep.toString() });
+ }
+
+ if (config?.grepInvert) {
+ startLaunchObj.attributes.push({ key: 'grepInvert', value: config.grepInvert.toString() });
+ }
+
const { tempId, promise } = this.client.startLaunch(startLaunchObj);
this.addRequestToPromisesQueue(promise, 'Failed to start launch.');
this.launchId = tempId;
@@ -361,6 +377,12 @@ export class RPReporter implements Reporter {
if (this.isLaunchFinishSend) {
return;
}
+
+ const taggedTestTitle = test.title;
+ const untaggedTestTitle = this.#extractTagsFromTitle(test.title);
+
+ test.title = untaggedTestTitle;
+
const playwrightProjectName = this.createSuites(test);
const fullSuiteName = getCodeRef(test, test.parent.title);
@@ -374,14 +396,20 @@ export class RPReporter implements Reporter {
test.title,
!includePlaywrightProjectNameToCodeReference && playwrightProjectName,
);
+
const { id: parentId } = parentSuiteObj;
+
const startTestItem: StartTestObjType = {
- name: test.title,
+ name: untaggedTestTitle,
startTime: this.client.helpers.now(),
type: TEST_ITEM_TYPES.STEP,
codeRef,
retry: test.results?.length > 1,
};
+
+ const attributes = this.#getAttributesFromTitle(taggedTestTitle);
+ if (attributes.length) startTestItem.attributes = attributes;
+
const stepObj = this.client.startTestItem(startTestItem, this.launchId, parentId);
this.addRequestToPromisesQueue(stepObj.promise, 'Failed to start test.');
this.testItems.set(test.id, {
@@ -635,4 +663,22 @@ export class RPReporter implements Reporter {
printsToStdio(): boolean {
return false;
}
+
+ #extractTagsFromTitle(title: string): string {
+ const tagRegex = /^@\w+\s*|\s*@\w+$/g;
+ const titleWithoutTags = title.replace(tagRegex, '');
+ const trimmedTitle = titleWithoutTags.trim();
+
+ return trimmedTitle;
+ }
+
+ #getAttributesFromTitle(title: string): Attribute[] {
+ const attributes = title.match(/@(\w+)(?::(\w+))?/g)?.map((tag) => {
+ const [key, value] = tag.slice(1).split(':');
+
+ return value ? { key, value } : { value: key };
+ });
+
+ return attributes || [];
+ }
}