问题
I would like to test my React Native application using screenshots. The UIAutomation javascript file will be executed by fastlane and should deliver me all subviews I need. This part works fine.
My main problem is that I don't understand how I may have an element clicked. Every example I found was plain objective-c and used standard elements for navigation like a tab bar. My application has a Burger Icon, which has a click event on the TouchableHighlight
which opens a menu. I am searching for a possibility to reference a single TouchableHighlight
element in order to interact with it.
Bonus points for such answers, which don't have me to write Objective-C.
回答1:
Fastlane (more specific snapshot) has deprecated UI Automation for UI Tests. In case you need to update the gems, your UIA javascript won't work for UI Tests (which are written in Obj C or Swift)
Why change to UI Tests?
UI Automation is deprecated UI Tests will evolve and support even more features in the future UI Tests are much easier to debug UI Tests are written in Swift or Objective C UI Tests can be executed in a much cleaner and better way
https://github.com/fastlane/snapshot
Looks like someone else using React Native made a little progress with UI Testing and Snapshot: https://github.com/fastlane/snapshot/issues/267
回答2:
I'm not familiar with fastlane, but you might want to give Jest a try since it's officially supported. They admittedly don't have full coverage, and it's quite possible you'll have to roll your own solution in some cases given how young react native is, but this ought to get you started on the right foot Snapshot Tests (iOS only)
回答3:
Note: we're using detox for our tests, so I'm using device.getPlatform()
to test for iOS or Android.
What I ended up doing is a mixture of JavaScript libs (pixelmatch and pngjs), using fs and using command line commands (xcrun simctl
and adb
).
const {device} = require('detox');
const {execSync} = require('child_process');
const fs = require('fs');
const {existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync} = fs;
const PNG = require('pngjs').PNG;
const pixelmatch = require('pixelmatch');
const IOS_SCREENSHOT_OPTIONS = {
timeout: 1000,
killSignal: 'SIGKILL'
};
function getScreenShotDirectory() { ... }
function getActualFileName(testName) { ... }
function getExpectedFileName(testName) { ... }
function getDiffFileName(testName) { ... }
async function takeScreenshot(testName) {
const actualFileName = getActualFileName(testName);
const directoryName = getScreenShotDirectory();
if (!existsSync(directoryName)) {
mkdirSync(directoryName, {recursive: true});
}
if (device.getPlatform() === 'ios') {
execSync(`xcrun simctl io booted screenshot "${actualFileName}"`, IOS_SCREENSHOT_OPTIONS);
await removeIosStatusBar(actualFileName);
} else {
execSync(`adb exec-out screencap -p > "${actualFileName}"`);
}
}
const compareScreenshot = async testName => {
const actualFileName = getActualFileName(testName);
await takeScreenshot(testName);
const expectedFileName = getExpectedFileName(testName);
const actualImage = PNG.sync.read(readFileSync(actualFileName));
if (!existsSync(expectedFileName)) {
console.warn(`No expected image for ${testName} @ ${expectedFileName}`);
return false;
}
const expectedImage = PNG.sync.read(readFileSync(getExpectedFileName(testName)));
const {width, height} = actualImage;
const diffImage = new PNG({width, height});
const numDiffPixels = pixelmatch(actualImage.data, expectedImage.data, diffImage.data, width, height);
if (numDiffPixels === 0) {
unlinkSync(actualFileName);
return true;
} else {
const percentDiffPixels = numDiffPixels / (width * height);
console.warn(
`Images are different ${testName} numDiffPixels=${numDiffPixels} percentDiffPixels=${percentDiffPixels}`
);
writeFileSync(getDiffFileName(testName), PNG.sync.write(diffImage));
return false;
}
};
To improve your testing results you should use Android's demo mode, for example:
execSync('adb shell settings put global sysui_demo_allowed 1');
execSync('adb shell am broadcast -a com.android.systemui.demo -e command ...');
execSync('adb shell am broadcast -a com.android.systemui.demo -e command exit');
And from xcode 11 you have:
execSync('xcrun simctl status_bar <device> override ...')
I removed the status bar from iOS using the following code (but it reduces performance):
const IOS_STATUS_BAR_HEIGHT = 40;
async function removeIosStatusBar(imageFileName) {
return new Promise((resolve, reject) => {
const image = PNG.sync.read(readFileSync(imageFileName));
let {width, height} = image;
height -= IOS_STATUS_BAR_HEIGHT;
const dst = new PNG({width, height});
fs.createReadStream(imageFileName)
.pipe(new PNG())
.on('error', error => reject(error))
.on('parsed', function () {
this.bitblt(dst, 0, IOS_STATUS_BAR_HEIGHT, width, height, 0, 0);
dst
.pack()
.pipe(fs.createWriteStream(imageFileName))
.on('error', error => reject(error))
.on('finish', () => resolve(imageFileName));
});
});
}
回答4:
Create a new project.
$ react-native -v
react-native-cli: 2.0.1
$ react-native init NativeSnapshots
$ cd NativeSnapshots
$ react-native run-ios
Test it works, launch welcome screen.
$ cd ios
$ fastlane snapshot init
fastlane output:
[14:37:56]: For more information, check out https://docs.fastlane.tools/getting-started/ios/setup/#use-a-gemfile
✅ Successfully created SnapshotHelper.swift './SnapshotHelper.swift'
✅ Successfully created new Snapfile at './Snapfile'
-------------------------------------------------------
Open your Xcode project and make sure to do the following:
1) Add a new UI Test target to your project
2) Add the ./fastlane/SnapshotHelper.swift to your UI Test target
You can move the file anywhere you want
3) Call `setupSnapshot(app)` when launching your app
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
4) Add `snapshot("0Launch")` to wherever you want to create the screenshots
More information on GitHub: https://github.com/fastlane/fastlane/tree/master/snapshot
Step 1: Add a new UI Test target to your project
Xcode Version 8.3.3 > Open NativeSnapshots.xcodeproj
File > New > Target > iOS UI Testing Bundle
Step 2: Add the ./fastlane/SnapshotHelper.swift to your UI Test target
Highlight NativeSnapshotsUITests
File > Add Files to NativeSnapshots
Select ./fastlane/SnapshotHelper.swift, Enter
Step 3: Call setupSnapshot(app) when launching your app
Open NativeSnapshotsUITests/NativeSnapshotsUITests.swift
in Xcode.
Replace:
XCUIApplication().launch()
With:
let app = XCUIApplication()
setupSnapshot(app)
app.launch()
Step 4: Add snapshot("0Launch")
to wherever you want to create the screenshots
Add snapshot call in testExample() in UI Tests.
func testExample() {
snapshot("0Launch")
}
Edit the Snapfile to avoid a huge matrix.
devices([
"iPhone 6"
])
languages([
"en-US"
])
scheme "NativeSnapshots"
It should be ready to go.
$ cd ios && fastlane snapshot
Copied from aj0strow
来源:https://stackoverflow.com/questions/34302710/has-anyone-used-ui-testing-with-react-native