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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ The plugin will not be active unless turned on when invoking the Appium server:

`appium server -ka 800 --use-plugins=appium-interceptor -pa /wd/hub`

## Custom certificate

If you need to use a custom certificate, it can be done by passing `certdirectory` as an argument of the plugin:

`appium server -ka 800 --use-plugins=appium-interceptor --plugin-appium-interceptor-certdirectory="<YOUR DIRECTORY>" -pa /wd/hub`

Please keep the same directory structure as the existing certificate folder.

## what does this plugin do?

For every appium session, interceptor plugin will start a proxy server and updates the device proxy settings to pass all network traffic to proxy server. Mocking is disabled by default and can be enabled from the test by passing `appium:intercept : true` in the desired capability while creating a new appium session.
Expand Down
2 changes: 1 addition & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Mock configuration is a json object that defines the specification for filtering
| statusCode | number | no | Updates the response status code with the given value | To simulate any unexpected error you can send some of the below statusCode <br> 1. `500` - Internal server error <br> 2. `400` - Bad request <br> 3. `401` - Unauthorized |
| responseHeaders | object | no | Map of key value pairs to be added or removed from the response header | Same syntax as `headers` key. But this will update the response header |
| responseBody | object | no | This will replace the original response data returned by the api server and updates it with new data | Passing the config as `{"url" : "/api/login/g" , "responseBody": "{\"error\": \"User account locked\"}", `statusCode`: 400 }` will simulate a error scenario when logged in with any user credentilas |
| updateResponseBody | string | no | This is similar to responseBody but instead of fully mocking the server response, you can replace any value in the response using Regular expression or jsonpath | Consider you application returns user data as `{\"username\": \"someusername\", \"email\": \"[email protected]\", \"isAdmin\" : \"false\"}` as a response for get user api request and you want to update the values for email and IsAdmin fiels, then you can pass <br> `{"updateRequestBody": [{ "jsonPath": "$.email", "[email protected]" }, { "jsonPath": "$.isAdmin", "true" }]}` and it will update the email and isAdmin field before sending the response back to the application|
| updateResponseBody | string | no | This is similar to responseBody but instead of fully mocking the server response, you can replace any value in the response using Regular expression or jsonpath | Consider you application returns user data as `{\"username\": \"someusername\", \"email\": \"[email protected]\", \"isAdmin\" : \"false\"}` as a response for get user api request and you want to update the values for email and IsAdmin fiels, then you can pass <br> `{"updateRequestBody": [{ "jsonPath": "$.email", value: "[email protected]" }, { "jsonPath": "$.isAdmin", value: "true" }]}` and it will update the email and isAdmin field before sending the response back to the application|


## Commands:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@
"$schema": "http://json-schema.org/draft-07/schema",
"additionalProperties": false,
"description": "Appium configuration schema for the interceptor plugin.",
"properties": {},
"properties": {
"certdirectory": {
"type": "string"
}
},
"title": "Appium interceptor plugin",
"type": "object"
},
Expand Down
5 changes: 0 additions & 5 deletions src/config.ts

This file was deleted.

9 changes: 9 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import path from 'path';

export interface IPluginArgs {
certdirectory: string;
}

export const DefaultPluginArgs: IPluginArgs = {
certdirectory: path.join(__dirname, '..', 'certificate'),
};
7 changes: 6 additions & 1 deletion src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BasePlugin } from 'appium/plugin';
import http from 'http';
import { Application } from 'express';
import { CliArg, ISessionCapability, MockConfig, RecordConfig, RequestInfo, ReplayConfig, SniffConfig } from './types';
import { DefaultPluginArgs, IPluginArgs } from './interfaces';
import _ from 'lodash';
import { configureWifiProxy, isRealDevice } from './utils/adb';
import { cleanUpProxyServer, sanitizeMockConfig, setupProxyServer } from './utils/proxy';
Expand All @@ -10,6 +11,7 @@ import logger from './logger';
import log from './logger';

export class AppiumInterceptorPlugin extends BasePlugin {
private pluginArgs: IPluginArgs = Object.assign({}, DefaultPluginArgs);
static executeMethodMap = {
'interceptor: addMock': {
command: 'addMock',
Expand Down Expand Up @@ -68,7 +70,9 @@ export class AppiumInterceptorPlugin extends BasePlugin {
};

constructor(name: string, cliArgs: CliArg) {
log.debug(`📱 Plugin Args: ${JSON.stringify(cliArgs)}`);
super(name, cliArgs);
this.pluginArgs = Object.assign({}, DefaultPluginArgs, cliArgs as unknown as IPluginArgs);
}

static async updateServer(expressApp: Application, httpServer: http.Server, cliArgs: CliArg) {}
Expand All @@ -89,6 +93,7 @@ export class AppiumInterceptorPlugin extends BasePlugin {
const mergedCaps = { ...caps.alwaysMatch, ..._.get(caps, 'firstMatch[0]', {}) };
const interceptFlag = mergedCaps['appium:intercept'];
const { deviceUDID, platformName } = response.value[1];
const certDirectory = this.pluginArgs.certdirectory;
const sessionId = response.value[0];
const adb = driver.sessions[sessionId]?.adb;

Expand All @@ -98,7 +103,7 @@ export class AppiumInterceptorPlugin extends BasePlugin {
return response;
}
const realDevice = await isRealDevice(adb, deviceUDID);
const proxy = await setupProxyServer(sessionId, deviceUDID, realDevice);
const proxy = await setupProxyServer(sessionId, deviceUDID, realDevice, certDirectory);
await configureWifiProxy(adb, deviceUDID, realDevice, proxy);
proxyCache.add(sessionId, proxy);
}
Expand Down
9 changes: 6 additions & 3 deletions src/scripts/test-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import { ADBInstance, UDID, configureWifiProxy, isRealDevice, openUrl } from '..
import { v4 as uuid } from 'uuid';
import { setupProxyServer } from '../utils/proxy';
import { Proxy } from '../proxy';
import path from 'path';

type VerifyOptions = {
udid: string;
certdirectory: string;
};

const defaultOptions: VerifyOptions = {
udid: '',
certdirectory: path.join(__dirname, '..', 'certificate'),
};

const MOCK_BACKEND_HTML = `<html><head><title>Appium Mock</title></head>
Expand Down Expand Up @@ -76,9 +79,9 @@ async function addMock(proxy: Proxy) {
});
}

async function verifyDeviceConnection(adb: ADBInstance, udid: UDID) {
async function verifyDeviceConnection(adb: ADBInstance, udid: UDID, certDirectory: string) {
const realDevice = await isRealDevice(adb, udid);
const proxy = await setupProxyServer(uuid(), udid, realDevice);
const proxy = await setupProxyServer(uuid(), udid, realDevice, certDirectory);
addMock(proxy);
await configureWifiProxy(adb, udid, realDevice, proxy);
await openUrl(adb, udid, MOCK_BACKEND_URL);
Expand All @@ -102,7 +105,7 @@ async function main() {
const options = getOptions();
const udid = await pickDeviceToTest(adb, options);
await registerExitHook(adb, udid);
await verifyDeviceConnection(adb, udid);
await verifyDeviceConnection(adb, udid, options.certdirectory);
}

main().catch(console.log);
15 changes: 9 additions & 6 deletions src/utils/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { Proxy } from '../proxy';
import ip from 'ip';
import os from 'os';
import path from 'path';
import config from '../config';
import fs from 'fs-extra';
import { minimatch } from 'minimatch';
import http from 'http';
Expand Down Expand Up @@ -112,9 +111,10 @@ export function modifyResponseBody(ctx: IContext, mockConfig: MockConfig) {
export async function setupProxyServer(
sessionId: string,
deviceUDID: string,
isRealDevice: boolean
isRealDevice: boolean,
certDirectory: string,
) {
const certificatePath = prepareCertificate(sessionId);
const certificatePath = prepareCertificate(sessionId, certDirectory);
const port = await getPort();
const _ip = isRealDevice ? 'localhost' : ip.address('public', 'ipv4');
const proxy = new Proxy({ deviceUDID, sessionId, certificatePath, port, ip: _ip });
Expand All @@ -131,9 +131,12 @@ export async function cleanUpProxyServer(proxy: Proxy) {
fs.rmdirSync(proxy.certificatePath, { recursive: true, force: true });
}

function prepareCertificate(sessionId: string) {
function prepareCertificate(sessionId: string, certDirectory: string) {
const sessionCertDirectory = path.join(os.tmpdir(), sessionId);
fs.copySync(config.certDirectory, sessionCertDirectory);
if(!fs.existsSync(certDirectory)){
throw new Error(`Error certDirectory doesn't exist (${certDirectory})`);
}
fs.copySync(certDirectory, sessionCertDirectory);
return sessionCertDirectory;
}

Expand Down Expand Up @@ -203,7 +206,7 @@ export function doesUrlMatch(pattern: UrlPattern, url: string) {
? jsonOrStringUrl.test(url)
: minimatch(url, jsonOrStringUrl);
} catch (err) {
log.error(`Error validaing url ${pattern} against url ${url}`);
log.error(`Error validating url ${pattern} against url ${url}`);
return false;
}
}
Expand Down