wcxc v1.0.0
#API Testing Framework
This framework is designed for end-to-end tests on API level. You cannot interact with UI elements (please refer to Sled, Java framework or any other relevant tool for this)
Currently only REST API is supported. It is possible that in future we also support RPC calls. For now, if you need to test a pure RPC endpoint, we recommend you ask the developers to expose it via HTTP. Check with us for the details.
Get Started
To start developing
- WIX VPN should be connected!
npm config set registry http://npm.dev.wixpress.com/
- add WIX registry- Install yarn
git clone git@github.com:wix-private/api-tests.git
yarn
in project root directory - download all packages
To run tests
yarn test
- run all the tests from 'test' folderjest -t 'describeString itString'
- run a test named itString from suite named describeStringjest -t 'itString'
- run a test named itString if there's no describe blockjest path/to/file/with/tests.spec.ts
- run all tests in the specified class
To create your first test
- Copy CURL of the request you want to test.
- Run
yarn curl:to-test-code <paste your curl here>
- it will print the code you need for your first test. - Copy the output or just simply add
| pbcopy
(for MacOS) at the end of the previous command - it will copy the output instead of printing it (yarn curl:to-test-code <your curl> | pbcopy
) - Create a file with .spec.ts extension in the src/test/ folder.
- Paste the copied code to the file.
- Add/modify assertions.
To create next tests
It's also possible to convert a CURL to a request code only and add use it in your tests. In order to to it:
- Add an empty test to your .spec.ts file.
- Copy CURL of the request you want to test.
- Run
yarn curl:to-request-code <paste your curl here> | pbcopy
- it will copy the request code. - Paste the copied code to your test.
- Add assertions you need.
Execute CURL via the framework
- Copy CURL of the request you want to test.
- Run
yarn curl:execute <paste your curl here>
- response will be printed
To create a service
We use services as a layer between tests and http core. The services contain functions which can be used and reused in tests.
Let's assume we have the following test:
it("sends post request as example", async function () {
const request = new HttpRequest();
request.method = HttpMethods.POST;
request.baseUrl = "https://enevqalovpju.x.pipedream.net";
request.addHeader("Content-Type", "application");
request.body = { name: "Yoda" };
const response = await send(request);
const expected = '{"success":true}';
expect(JSON.stringify(response.parsedBody)).to.be.eq(expected);
expect(response.status).to.be.eq(200);
expect(response.statusText).to.be.eq("OK");
});
And we need to create a service out of it. Let's do it:
- Create a service class in src/main/drivers
ExampleService.ts
- Inherit your service class from BaseService
export class ExampleService extends BaseService {}
- Add a constructor
whereconstructor() { super(baseUrl); }
baseUrl
is the part of endpoint URL which is common for all functions in the service. You can also add an Identity here. - Create a function in a class which will send your API call and return a response
HttpResponse has typeasync exampleCall(): Promise<HttpResponse<any>> { }
any
now so it will return just general object. It is enough for the tests where you don't need to get access to the response's payload. Otherwise, we need to make it return the type we expect in response (the type should have all the fields returned object has).
For the most cases Ambassador library has needed type, you just have to find it. In order to do it start typing the type you expect in response inside the HttpResponse <> generic brackets instead ofany
type. It should look lke<HttpResponse <GetTransactionResponse>
For other cases create needed type in src/main/data/types.ts and use it.export type SuccessResponse = { success: boolean; };
Now you can move all the code regarding API call from your test to the function:
async exampleCall(): Promise<HttpResponse<SuccessResponse>> { const request = new HttpRequest(); request.method = HttpMethods.POST; request.baseUrl = 'https://enevqalovpju.x.pipedream.net'; request.addHeader("Content-Type", "application"); request.body = {"name": "Yoda"}; return send<SuccessResponse>(request); }
You can also override any fields of request body by passing PartialOf as an argument to the function and merge it with default body inside:
async exampleCall(customPayload?: PartialOf<SuccessRequest>): Promise<HttpResponse <SuccessResponse>> { const request = new HttpRequest(); request.method = HttpMethods.POST; request.baseUrl = 'https://enevqalovpju.x.pipedream.net'; request.addHeader("Content-Type", "application"); const defaultBody = {"name": "Yoda"}; request.body = merge(defaultBody, customPayload); return send<SuccessResponse>(request); }
If your body JSON is large it is highly recommended to split it by objects and build your body out of them. The example can be found in TransactionService.ts
Now you are ready to modify your test using the created service:
it('sends post request as example', async function () { const service = new ExampleService(); const customPayload: PartialOf<SuccessRequest> = { name: "Anakin" }; const response = await service.exampleCall(); expect(response.parsedBody.success).to.be.true; expect(response.status).to.be.eq(200); expect(response.statusText).to.be.eq("OK"); }
Features
Simple interface for handling REST requests/responses
- Add all required headers and cookies
- Add body as a simple JSON-like object or a typed object (see section about wix-ambassador)
- Add all required authorizations (as user, as app, etc.) with identities (see Identities section)
const request = new HttpRequest();
request.method = HttpMethods.POST;
request.baseUrl = "https://enevqalovpju.x.pipedream.net";
request.addHeader("Content-Type", "application");
request.body = { name: "Yoda" };
const response = await send(request);
You can validate the status of your response:
expect(response.status).to.eq(200);
expect(response.statusText).to.eq("My successful response");
The data you received from the REST endpoint is available in response.parsedBody
expect(response.parsedBody.transaction.authorization.status).to.eq("SUCCEEDED");
Identities
Many WIX endpoints require that the call is authorized either by user (wixSession2
cookie), or by app calling this endpoint (Authorization
header).
You can add one or several identities to your requests.
App Identity
- Precondition: you need an app in dev.wix.com with all necessary permissions
- Store your app data in code as
AppInstanceData
(see existing apps for details) - Check the required authorization token prefix in Fire Console (can be
SRV.JWS
, orJWT
, or else). Create an AppIdentity object with required authorization token:
const appIdentity = new AppIdentity(app, "SRV.JWS");
Add this identity to your request:
request.addIdentities(appIdentity);
User Identity
- Store your test user data in code as
UserData
Create a UserIdentity object:
const userIdentity = new WixUserIdentity(user);
Add this identity to your request:
request.addIdentities(userIdentity);
Type safety with @wix-ambassador
@wix-ambassador is a library which provides types for all requests and responses used in Wix services. Example:
// Type describing an item purchased via wix-payments
// @wix-ambassador library
export interface V3Item {
category?: V3ItemCategory;
id?: String;
quantity?: Int;
title?: String;
unitPrice?: Long;
}
// Using it in our code
export const item: V3Item = {
id: "ItemID",
title: "ItemTitle",
unitPrice: 100,
quantity: 1,
category: V3ItemCategory.DIGITAL,
};
We use types from wix-ambassador to send requests:
// Charge for existing order
import {
ChargeForOrderRequest,
ChargeIntent,
} from "@wix/ambassador-payment-services-web/types";
const request = this.getBaseRequest();
const payload: ChargeForOrderRequest = {
initializeOnly: false,
intent: ChargeIntent.SALE,
orderId: testOrderId,
payment: testPayment,
};
request.body = payload;
And to get and validate responses:
const result: HttpResponse<ChargeResponse> = await chargeForOrder(
orderId,
CARDS.wixPaymentsCard
);
expect(result.status).toBe(200);
expect(result.parsedBody.charge.state.transactionStatus.status).to.be.eq(
"APPROVED"
);
Partial types
When you send a custom request, you don't always want to specify all fields. Most of them can have default values.
For this, we introduced PartialOf<SomeType>
Usage:
- We will take CaptureTransaction method as an example
- It requires @wix-ambassador type CaptureTransactionRequest
If user doesn't provide any custom data, we will send the default payload:
const defaultPayload: CaptureTransactionRequest = { testMode: true, accountId: tenantAccountId, transactionId: transactionId };
In tests, however, we need to send custom data (e.g. capture a certain amount of money, not all of them)
- But all the other fields can have default value
For this, we use the type
PartialOf<CaptureTransactionRequest>
:const payloadWithCustomAmount: PartialOf<CaptureTransactionRequest> = { amount: 50 };
PartialOf<SomeType>
suggests you all fields fromSomeType
and checks that you don't make mistakes in their names. But all these fields become optional, and you no longer need to set all of them if you need to change just 1.
Assertions
We use Chai library to make all checks in our tests. Full Chai documentation can be found in https://www.chaijs.com/api/
Chai also provides a list of plugins for specific assertions (for example, validating arrays).
To add a new Chai plugin, you can do the following:
- Check that this plugin is not yet present in the framework (check dependencies in package.json)
- If it is already added, find its usage in some .spec.ts file and copy it.
- Otherwise, run a shell command
yarn add %package-name%
- It is also recommended to run
yarn add @types/%package-name%
to add Typescript support. Note that not all plugins provide that! In your .spec.ts file, add the following imports:
import chai from 'chai'; import YourChaiPlugin = require('chai-plugin-library-name'); chai.use(YourChaiPlugin);
If something goes wrong, and the previous step won't compile, ask framework owners, and we'll add this library for you.
How to store sensitive data
Passwords, secret keys or any other sensitive information can't be stored in repo in as a plain text - it should be encrypted. For encryption/decryption we use AUTOMATION_MASTER_KEY which should be set in local env. To encrypt a key execute the following script in CLI:
$ yarn secret:encrypt "your-secret-key"
To encrypt an object:
$ yarn secret:encrypt-object '{"key":"value"}'
Note that both key and value should be in quotes.
Copy the output and store it in the src/main/core/utils/secret.ts file. To get decrypted value create a function in the same file with the following code:
return simpleCrypto.decrypt("your encrypted value here");
The function will return your original sensitive info.
You can also decrypt your encrypted key using CLI. Use it just to check that your encrypted key decrypts correctly.
$ yarn secret:decrypt "your-encrypted-key"
or
$ yarn secret:decrypt-object "your-encrypted-object"
How to add your tests to Team City run
Add a script to package.json with the following content:
"test:name-of-entity-you-are-testing": "script-which-runs-your-tests"
In Team City create a build configuration based on
AIT API Tests
template.- Add Build Step -> Command Line.
- Add custom script:
yarn test:name-of-entity-you-are-testing --testResultsProcessor=jest-teamcity-reporter
- Add Trigger -> Schedule Trigger.
- Set the schedule to run the tests.
4 years ago