Skip to content

Commit

Permalink
Merge pull request #4429 from wix/fix/disable-cors-workaround-by-default
Browse files Browse the repository at this point in the history
fix(iOS): add launch-argument to disable WebKit security.
  • Loading branch information
asafkorem authored Mar 30, 2024
2 parents a16a83d + 7c44093 commit c92ee73
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 43 deletions.
8 changes: 7 additions & 1 deletion detox/ios/Detox/Invocation/WKWebViewConfiguration+Detox.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,15 @@ + (void)load {
}

- (void)dtx_setPreferences:(WKPreferences *)preferences {
DTXPreferencesSetWebSecurityEnabled(preferences, NO);
if ([self shouldDisableWebKitSecurity]) {
DTXPreferencesSetWebSecurityEnabled(preferences, NO);
}

[self dtx_setPreferences:preferences];
}

- (BOOL)shouldDisableWebKitSecurity {
return [NSUserDefaults.standardUserDefaults boolForKey:@"detoxDisableWebKitSecurity"];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ extension WebCodeBuilder {
var frameElements = getAllElements(frameDoc, selector);
elements = elements.concat(frameElements);
} catch(e) {
throw e;
/* Probably issues accessing iframe documents (CORS restrictions) */
}
}
return elements;
};
var allElements = getAllElements(document, '\(baseSelector)');
Expand Down
2 changes: 1 addition & 1 deletion detox/ios/DetoxSync
106 changes: 66 additions & 40 deletions detox/test/e2e/29.webview.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const {expectElementSnapshotToMatch} = require("./utils/snapshot");
const {waitForCondition} = require("./utils/waitForCondition");
const {expectToThrow} = require('./utils/custom-expects');

const jestExpect = require('expect').default;

const MockServer = require('../mock-server/mock-server');

describe('Web View', () => {
describe('WebView', () => {
beforeEach(async () => {
await device.reloadReactNative();
await element(by.text('WebView')).tap();
Expand All @@ -31,15 +33,15 @@ describe('Web View', () => {
});

it('should raise an error when element does not exists but expect to exist', async () => {
await jestExpect(async () => {
await expectToThrow(async () => {
await expect(web.element(by.web.id('nonExistentElement'))).toExist();
}).rejects.toThrowError();
});
});

it('should raise an error when does element not exists at index', async () => {
await jestExpect(async () => {
await expectToThrow(async () => {
await expect(web.element(by.web.tag('p')).atIndex(100)).toExist();
}).rejects.toThrowError();
});
});
});

Expand Down Expand Up @@ -266,9 +268,9 @@ describe('Web View', () => {
it('should raise error when script fails', async () => {
const headline = web.element(by.web.id('pageHeadline'));

await jestExpect(async () => {
await expectToThrow(async () => {
await headline.runScript('(el) => { el.textContent = "Changed"; throw new Error("Error"); }');
}).rejects.toThrowError();
});
});
});

Expand All @@ -295,35 +297,6 @@ describe('Web View', () => {
});
});

describe(':ios: inner frame', () => {
/** @type {Detox.WebViewElement} */
let webview;
const mockServer = new MockServer();

beforeAll(async () => {
mockServer.init();

if (device.getPlatform() === 'android') {
// Android needs to reverse the port in order to access the mock server
await device.reverseTcpPort(mockServer.port);
}
});

afterAll(async () => {
await mockServer.close();
});

beforeEach(async () => {
await element(by.id('toggle3rdWebviewButton')).tap();
webview = web(by.id('webView'));
});

it('should find element in inner frame', async () => {
await expect(webview.element(by.web.tag('h1'))).toExist();
await expect(webview.element(by.web.tag('h1'))).toHaveText('Hello World!');
});
});

describe('multiple web-views scenario',() => {
/** @type {Detox.WebViewElement} */
let webview;
Expand Down Expand Up @@ -366,18 +339,71 @@ describe('Web View', () => {
});

it('should throw on index out of bounds', async () => {
await jestExpect(async () => {
await expectToThrow(async () => {
await expect(web(by.id('webView')).atIndex(2).element(by.web.id('message'))).toExist();
}).rejects.toThrowError();
});
});
});

// Not implemented yet
it(':android: should throw on usage of atIndex', async () => {
await jestExpect(async () => {
await expectToThrow(async () => {
await expect(web(by.id('webView')).atIndex(0).element(by.web.id('message'))).toExist();
}).rejects.toThrowError();
});
});
});
});
});

describe(':ios: WebView CORS (inner frame)', () => {
/** @type {Detox.WebViewElement} */
let webview;
const mockServer = new MockServer();

beforeAll(async () => {
mockServer.init();

if (device.getPlatform() === 'android') {
// Android needs to reverse the port in order to access the mock server
await device.reverseTcpPort(mockServer.port);
}
});

afterAll(async () => {
await mockServer.close();
});

const launchAndNavigateToInnerFrame = async (shouldDisableWebKitSecurity) => {
await device.launchApp({
newInstance: true,
launchArgs: {
detoxDisableWebKitSecurity:
shouldDisableWebKitSecurity !== undefined ? (shouldDisableWebKitSecurity ? 'true' : 'false') : undefined,
},
});

await element(by.text('WebView')).tap();
await element(by.id('toggle3rdWebviewButton')).tap();

webview = web(by.id('webView'));
};

it('should find element in cross-origin frame when `detoxDisableWebKitSecurity` is `true`', async () => {
await launchAndNavigateToInnerFrame(true);

await expect(webview.element(by.web.tag('h1'))).toExist();
await expect(webview.element(by.web.tag('h1'))).toHaveText('Hello World!');
});

it('should not find element in cross-origin frame when `detoxDisableWebKitSecurity` is `false`', async () => {
await launchAndNavigateToInnerFrame(false);

await expect(webview.element(by.web.tag('h1'))).not.toExist();
});

it('should not find element in cross-origin frame when `detoxDisableWebKitSecurity` is not set', async () => {
await launchAndNavigateToInnerFrame();

await expect(webview.element(by.web.tag('h1'))).not.toExist();
});
});
19 changes: 19 additions & 0 deletions docs/api/device.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,25 @@ await device.launchApp({
});
```
#### 12. `detoxDisableWebKitSecurity`—Disable WebKit Security (iOS Only)
Disables WebKit security on iOS. Default is `false`.
This is useful for testing web views with iframes that loads CORS-protected content.
:::caution Important
Some pages may not load correctly when WebKit security is disabled (for example, PCI DSS-compliant pages).
Disabling WebKit security may cause errors when loading pages that have strict security policies.
:::
```js
await device.launchApp({
launchArgs: { detoxDisableWebKitSecurity: true }
});
```
### `device.terminateApp()`
By default, `terminateApp()` with no params will terminate the app file defined in the current [`configuration`](../config/overview.mdx).
Expand Down
13 changes: 13 additions & 0 deletions docs/guide/testing-webviews.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ const elementByCSSSelector = web.element(by.web.cssSelector('#cssSelector'));
const elementAtIndex = web.element(by.web.id('identifier').atIndex(1));
```

### Bypass CORS Restrictions (iOS Only)

When testing web views, you may encounter Cross-Origin Resource Sharing (CORS) restrictions that prevent you from interacting with elements inside the web view.

At the moment, Detox is able to bypass CORS restrictions and other browser security features only on iOS, allowing you to interact with inner elements in cases of CORS restrictions (in most cases).

To bypass CORS restrictions on iOS, you can pass the [`detoxDisableWebKitSecurity`] launch argument. This argument will disable the WebKit security features, allowing Detox to interact with the WebView in a "Sandbox" environment.

```javascript
await device.launchApp({ launchArgs: { detoxDisableWebKitSecurity: true } });
```

## Step 3: Perform Actions

Actions allow you to interact with elements within a web view. The [Detox WebView APIs][actions-apis] provide various actions that can be invoked on inner elements.
Expand Down Expand Up @@ -165,3 +177,4 @@ it('should login successfully', async () => {
[run-script-api]: ../api/webviews.md#runscriptscript-args
[finding-inner-elements]: #step-2-finding-inner-elements
[at-index-api]: ../api/webviews.md#webnativematcheratindexindexelementmatcher
[`detoxDisableWebKitSecurity`]: ../api/device.md#12-detoxdisablewebkitsecuritydisable-webkit-security-ios-only

0 comments on commit c92ee73

Please sign in to comment.