How do I screenshot tests in React Native?

落花浮王杯 提交于 2019-12-10 15:39:28

问题


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 TouchableHighlightelement 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/33285111/how-do-i-screenshot-tests-in-react-native

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!