Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing issues with intergration tests not working #79

Merged
merged 30 commits into from
Sep 28, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- name: Docker version
run: docker -v
- name: Setting up a local Report Portal environment
run: sudo docker-compose -f docker-compose.yml -p reportportal up -d --force-recreate
run: sudo docker compose -f docker-compose.yml -p reportportal up -d --force-recreate
- name: Waiting for services to be up
run: ./scripts/healthcheck.bash
- name: Report Portal server status
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"run-full-unit-tests": "npm run build-local-reporter && npm run unit-tests && npm run unit-retry-tests && npm run unit-no-live-reporting-tests && npm run unit-display-debug-logs-tests",
"integration-tests": "npm run test ./tests/integration/integration.executor.ts -- --display-debug-logs",
"run-integration-tests": "npm run build-local-reporter && npm run integration-tests",
"download-report-portal-docker-compose-file": "curl https://raw.githubusercontent.com/reportportal/reportportal/a9f2fe18dfe9cb2a631d8a60fdbe254ced6963e7/docker-compose.yml -o docker-compose.yml",
"download-report-portal-latest-docker-compose-file": "curl https://raw.githubusercontent.com/reportportal/reportportal/master/docker-compose.yml -o docker-compose.yml",
"up": "docker compose -p reportportal up",
"start": "docker compose -p reportportal start",
Expand Down
22 changes: 12 additions & 10 deletions scripts/healthcheck.bash
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#!/bin/bash
set -eux
while true; do
# Run the command and capture the output
output=$(npm run status)

wait-for-status() {
echo "Waiting for services status"
echo ""
bash -c \
'while [[ "$(npm run status)" == *"(starting)"* ]];\
do echo "Waiting for services" && sleep 2;\
done'
}
wait-for-status
# Check if the output contains 'starting' or 'exited'
if echo "$output" | grep -q -e "starting" -e "exited"; then
echo "Detected 'starting' or 'exited' in the output. Sleeping for 2 seconds..."
sleep 2
else
echo "No 'starting' or 'exited' found in the output. Continuing..."
break
fi
done
24 changes: 13 additions & 11 deletions src/uat.js → src/api-testing-client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const axios = require('axios');

class UAT {
class ApiTestingClient {
constructor (options) {
this.baseURL = `${options.protocol}://${options.domain}${options.apiPath}`;
this.token = options.token;
Expand Down Expand Up @@ -61,7 +61,7 @@ class UAT {
});
const token = Buffer.from('ui:uiman').toString('base64');
this.setUiToken(token);
const response = await this.client.post(`/sso/oauth/token?${encodedURI}`);
const response = await this.client.post(`uat/sso/oauth/token?${encodedURI}`);
return this.handleResponse(response);
}
catch (error) {
Expand All @@ -70,18 +70,20 @@ class UAT {
}

/**
* Generating an API token
* @param {*} token The UI token to authenticate with
* @returns A response obj with the API token data
* We create an API key to use it later on in our tests.
* @param {*} token The UAT token to gain permissions to create an API key
* @param {*} userId The id of the user
* @param {*} name The name of the token
* @returns The api key object
*/
async generateApiToken(token) {
this.setApiToken(token);
async createApiKey(token, userId, name) {
try {
const response = await this.client.post('/sso/me/apitoken?authenticated=true');
this.setApiToken(token);
const response = await this.client.post(`api/users/${userId}/api-keys`, {name: name});
return this.handleResponse(response);
}
catch(error) {
return this.handleError(error);
catch (error) {
return this.handleError(error);
}
}

Expand Down Expand Up @@ -150,4 +152,4 @@ class UAT {
}
}

module.exports = UAT;
module.exports = ApiTestingClient;
68 changes: 52 additions & 16 deletions src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class API {
*/
async checkConnect () {
try {
return this.handleResponse(await this.client.get('/user'));
return this.handleResponse(await this.client.get('/users'));
}
catch (error) {
if(this.displayDebugLogs === true){
Expand All @@ -35,7 +35,7 @@ class API {
*/
async createLaunch (projectName, options) {
try {
return this.handleResponse(await this.client.post(`/${projectName}/launch`, options));
return this.handleResponse(await this.client.post(`/v1/${projectName}/launch`, options));
}
catch (error) {
return this.handleError(error);
Expand All @@ -50,7 +50,7 @@ class API {
*/
async finishLaunch (projectName, launchId, options) {
try {
return this.handleResponse(await this.client.put(`/${projectName}/launch/${launchId}/finish`, options));
return this.handleResponse(await this.client.put(`/v1/${projectName}/launch/${launchId}/finish`, options));
}
catch (error) {
return this.handleError(error);
Expand All @@ -65,7 +65,7 @@ class API {
*/
async forceStopLaunch (projectName, launchId, options) {
try {
return this.handleResponse(await this.client.put(`/${projectName}/launch/${launchId}/stop`, options));
return this.handleResponse(await this.client.put(`/v1/${projectName}/launch/${launchId}/stop`, options));
}
catch (error) {
return this.handleError(error);
Expand All @@ -79,7 +79,7 @@ class API {
*/
async createTestItem (projectName, options) {
try {
return this.handleResponse(await this.client.post(`/${projectName}/item`, options));
return this.handleResponse(await this.client.post(`/v1/${projectName}/item`, options));
}
catch (error) {
return this.handleError(error);
Expand All @@ -94,7 +94,7 @@ class API {
*/
async createChildTestItem (projectName, parentItem, options) {
try {
return this.handleResponse(await this.client.post(`/${projectName}/item/${parentItem}`, options));
return this.handleResponse(await this.client.post(`/v1/${projectName}/item/${parentItem}`, options));
}
catch (error) {
return this.handleError(error);
Expand All @@ -109,7 +109,7 @@ class API {
*/
async finishTestItem (projectName, testItemId, options) {
try {
return this.handleResponse(await this.client.put(`/${projectName}/item/${testItemId}`, options));
return this.handleResponse(await this.client.put(`/v1/${projectName}/item/${testItemId}`, options));
}
catch (error) {
return this.handleError(error);
Expand Down Expand Up @@ -159,36 +159,72 @@ class API {
headers: { 'Content-type': `multipart/form-data; boundary=${MULTIPART_BOUNDARY}`, 'Authorization': `Bearer ${this.token}` }
});

await instance.post(`${this.baseURL}/${projectName}/log`, this.buildMultiPartStream([options], {
await instance.post(`${this.baseURL}/v1/${projectName}/log`, this.buildMultiPartStream([options], {
name: options.file.name,
type: 'image/png',
content: fs.readFileSync(fullPath)
}, MULTIPART_BOUNDARY));
}
else this.handleResponse(await this.client.post(`/${projectName}/log`, options));
else this.handleResponse(await this.client.post(`/v1/${projectName}/log`, options));
}
catch (error) {
this.handleError(error);
}
}

/**
* Retrieving all logs in a project
* @param {*} projectName The name of the project
* @returns A list of logs
* Retrieves a list of the project latest launches
* @param {*} projectName The project name
* @returns A list of the latest project launches
*/
async getLaunches(projectName) {
try {
const response = await this.client.get(`/v1/${projectName}/launch/latest`);
return this.handleResponse(response).content;
}
catch (error) {
return this.handleError(error);
}
}

/**
* Retrieves a list of test items
* @param {*} projectName The project name
* @param {*} launchId The launch id
* @returns A list of test items that are part of a project and a launch
*/
async getTestItems(projectName, launchId) {
try {
const response = await this.client.get(`/v1/${projectName}/item?filter.eq.launchId=${launchId}&isLatest=false&launchesLimit=0`);
return this.handleResponse(response).content;
}
catch (error) {
return this.handleError(error);
}
}

/**
* Retrieves a list of logs under a test item
* @param {*} projectName The project name
* @param {*} testItemId The test item id
* @param {*} logLevel The log level. Default: info
* @returns A list of test item logs
*/
async getLogs(projectName) {
async getTestItemLogs(projectName, testItemId, logLevel='info') {
try {
const response = await this.client.get(`/${projectName}/log`);
return this.handleResponse(response);
const response = await this.client.post(`/v1/${projectName}/log/under`, {
itemIds: [testItemId],
logLevel: logLevel
});
return this.handleResponse(response)[testItemId];
}
catch (error) {
return this.handleError(error);
}
}

/**
* Checking if item is a valid JSON
* Checking if item is a valid JSON by attempting to parse it
* @param {*} json The string of the JSON
*/
isJSON (json) {
Expand Down
5 changes: 3 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,13 @@ exports['default'] = () => {
async reportLogs(testId, level, message, time, attachment) {
if(message !== undefined) {
const isJSON = this.reporter.client.isJSON(message) || Array.isArray(message);
const isException = isJSON && JSON.parse(message).errMsg !== undefined;
//If the log is a stacktrace, and we want to focus on printing the error message itself.
if(isJSON && JSON.parse(message).errMsg !== undefined) message = JSON.parse(message).errMsg;
if(isException) message = JSON.parse(message).errMsg;
//If the log is a JS Object
else if(isJSON) message = JSON.parse(message);
else if(typeof message === 'object') message = `"${message}"`;
message = this.reporter.client.isJSON(message) ? JSON.stringify(message): message;
message = isJSON ? JSON.stringify(message): message;
}
await this.reporter.sendTestLogs(testId, level, message, time, attachment);
},
Expand Down
2 changes: 1 addition & 1 deletion src/report-portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ReportPortal {
this.client = new RPClient({
protocol: (cliArguments.rprotocol) ? cliArguments.rprotocol: 'https',
domain: cliArguments.rdomain,
apiPath: '/api/v1',
apiPath: '/api',
token: cliArguments.rtoken,
});
this.connected = true;
Expand Down
18 changes: 11 additions & 7 deletions tests/integration/integration.executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@ import { loadArguments } from '../utils/cli-loader';
import createTestCafe from 'testcafe';
import { cliArguments } from 'cli-argument-parser';
import { expect } from 'chai';
import UAT from '../../src/uat.js'
import ApiTestingClient from '../../src/api-testing-client.js'
let testcafeServer: TestCafe;

describe('Performing Integration testing', async function() {
this.timeout(10 * 60 * 60 * 60);
before(async () => {
loadArguments();
const client = new UAT({
protocol: 'http',
domain: 'localhost:8080',
apiPath: '/uat',
const client = new ApiTestingClient({
protocol: cliArguments.rprotocol,
domain: cliArguments.rdomain,
apiPath: '/',
});

//Using the default user provided by report portal
const token = await client.getApiToken('default', '1q2w3e');
const apiToken = await client.generateApiToken(token.access_token);
cliArguments.rtoken = apiToken.access_token;
const apiToken = await client.createApiKey(token.access_token, 1, `testing-${new Date().getTime()}` );
cliArguments.rtoken = apiToken.api_key;
testcafeServer = await createTestCafe('localhost', 1337, 1338);
});

Expand All @@ -27,6 +29,7 @@ describe('Performing Integration testing', async function() {
});

it('Running TestCafe Tests', async () => {
cliArguments.rlaunch="TestCafe Tests"
const runner = testcafeServer.createRunner();
const failedCount = await runner
.src(['tests/integration/integration.testcafe.ts'])
Expand All @@ -37,6 +40,7 @@ describe('Performing Integration testing', async function() {
console.log('Tests failed: ' + failedCount);
});
it('Retry mechanism Tests', async () => {
cliArguments.rlaunch="Retry mechanism Tests"
const runner = testcafeServer.createRunner();
const failedCount = await runner
.src(['tests/integration/integration.retry.testcafe.ts'])
Expand Down
43 changes: 26 additions & 17 deletions tests/integration/integration.testcafe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,53 @@ fixture `First fixture`
.page('https://google.com')
.before(async () => {
api = new API({
protocol: 'http',
domain: 'localhost:8080',
apiPath: '/api/v1',
protocol: cliArguments.rprotocol,
domain: cliArguments.rdomain,
apiPath: '/api',
token: cliArguments.rtoken,
})
});
test('Taking screenshot', async () => {
await logAndVerify('About to take a screenshot');
await logAndVerify('Taking screenshot', 'About to take a screenshot');
await t.takeScreenshot()
await logAndVerify('The screenshot was succesfully taken!');
await logAndVerify('Taking screenshot', 'The screenshot was succesfully taken!');
})
test('Negative testing, verifying Error display', async () => {
await logAndVerify('About to fail..');
await logAndVerify(`${{obj: 'X', obj2: { x: 'Y'}}}`)
await logAndVerify({obj: 'X', obj2: { x: 'Y'}})
await logAndVerify('Negative testing, verifying Error display', 'About to fail..');
await logAndVerify('Negative testing, verifying Error display', `${{obj: 'X', obj2: { x: 'Y'}}}`)
await logAndVerify('Negative testing, verifying Error display', {obj: 'X', obj2: { x: 'Y'}})
await t.expect('X').eql('Y', 'OMG');
await logAndVerify('The test failed!');
await logAndVerify('Negative testing, verifying Error display', 'The test failed!');
})

fixture `Second fixture`
test.skip('Skipping the test', async () => {
await logAndVerify('The test is skipped. This log shoud not be appearing.');
await logAndVerify('Skipping the test', 'The test is skipped. This log shoud not be appearing.');
})
test('Basic print', async () => {
await logAndVerify('Printing the test contents');
await logAndVerify('Basic print', 'Printing the test contents');
})

/**
* Logging a message via console.log and verifying it exists in report portal
* @param logMsg The log message
*/
async function logAndVerify(logMsg: any) {
console.log(logMsg);
async function logAndVerify(testName: string, logMsg: any) {
const message = typeof logMsg !== 'string' ? JSON.stringify(logMsg): logMsg
console.log(message);
await sleepInSeconds(5 * 1000);
let logs = await api.getLogs(cliArguments.rproject);
let log = logs.content.filter(l => l.message === logMsg);
process.stdout.write(`\n[Test logs]: Found ${log.length} occurances for message '${logMsg}'\n`);
await t.expect(log.length).gte(1, `Log appearances for '${logMsg}'`);
let launches = await api.getLaunches(cliArguments.rproject);
launches = launches.filter(l => l.name === cliArguments.rlaunch)
const launchId = launches[0].id;
const items = await api.getTestItems(cliArguments.rproject, launchId, testName)

const item = items.find(item => item.name === testName && item.type === 'TEST')
const logs = await api.getTestItemLogs(cliArguments.rproject, item.id)

const filteredLogs = logs.filter(l => l.message === message);

process.stdout.write(`\n[Test logs]: Found ${filteredLogs.length} occurances for message '${message}'\n`);
await t.expect(filteredLogs.length).gte(1, `Log appearances for '${message}'`);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/mock.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Route, Request } from 'dmock-server';

export const mock: Route[] = [{
path: '/api/v1/user',
path: '/api/users',
method: 'get',
response: {}
},{
Expand Down
Loading