Skip to content

Instantly share code, notes, and snippets.

@janvennemann
Created April 22, 2020 09:34
Show Gist options
  • Save janvennemann/545641910c48d3f61bd0ea1b5809fd38 to your computer and use it in GitHub Desktop.
Save janvennemann/545641910c48d3f61bd0ea1b5809fd38 to your computer and use it in GitHub Desktop.
Firebase Test Lab Hook for Titanium
const win = Ti.UI.createWindow({ layout: 'vertical' });
const btn = Ti.UI.createButton({ title: 'Press Me!' });
win.add(btn);
const lbl = Ti.UI.createLabel({ text: 'Press the button!' });
win.add(lbl);
win.open();
const fs = require('fs');
const path = require('path');
/**
* This hook will configure UI tests using XCTest for your Titanium app.
*
* 1. Creates a new UI test target and sets the app as the testing target
* 2. Adds test cases from platform/ios/Tests to the project.
* 3. Patches the default Scheme to include a test plan which defines what tests to run.
* 3. Modifies `xcodebuild` to run with `build-for-testing` to generate xctestrun.
*/
exports.id = 'firebase.test.labs';
exports.init = (logger, config, cli) => {
// only enable when XCTEST env var is set
if (!process.env.XCTEST) {
return;
}
let platformDir
cli.on('build.pre.compile', (builder, done) => {
platformDir = path.join(builder.projectDir, 'platform', 'ios');
done();
})
cli.on('build.ios.xcodeproject', {
pre: (data, done) => {
const builder = data.ctx;
const appName = builder.tiapp.name;
const project = data.args[0];
// patch generate uuid function so we can use xcode project functions
// while keeping incremental build detection happy
project.generateUuid = builder.generateXcodeUuid.bind(builder)
// create UI test target
// the node xcode API doesn't know about UI test product type so cheat by
// specifing unit test type first and then change it to UI testing.
const target = project.addTarget('UITests', 'unit_test_bundle');
target.pbxNativeTarget.productType = 'com.apple.product-type.bundle.ui-testing';
// the product file gets messed up by default, fix it
project.removeProductFile('UITests');
project.removeFromPbxBuildFileSection({
fileRef: target.pbxNativeTarget.productReference,
basename: 'UITests.mdimporter'
});
const productFile = project.addProductFile('UITests.xctest', {
target: target.uuids
});
project.pbxFileReferenceSection()[productFile.fileRef].explicitFileType = 'wrapper.cfbundle';
project.addToPbxBuildFileSection(productFile);
target.pbxNativeTarget.productReference = productFile.fileRef
// update patched target
project.pbxNativeTargetSection()[target.uuid] = target.pbxNativeTarget;
// configure required build settings
const configListUuid = target.pbxNativeTarget.buildConfigurationList;
const configList = project.pbxXCConfigurationList()[configListUuid].buildConfigurations;
for (const configListEntry of configList) {
const config = project.pbxXCBuildConfigurationSection()[configListEntry.value];
const settings = config.buildSettings;
settings.PRODUCT_BUNDLE_IDENTIFIER = `"${builder.tiapp.id}UITests"`
settings.TEST_TARGET_NAME = `"${appName}"`;
settings.CODE_SIGN_IDENTITY = `"${builder.certDeveloperName}"`;
settings.CODE_SIGN_STYLE = "Manual";
settings.DEVELOPMENT_TEAM = builder.teamId;
settings.PROVISIONING_PROFILE = `"${builder.provisioningProfile.uuid}"`;
settings.PROVISIONING_PROFILE_SPECIFIER = `"${builder.provisioningProfile.name}"`;
}
// add target attribute to define test target
project.addTargetAttribute('TestTargetID', project.getFirstTarget().uuid, target);
// add new sources build phase to UI test target
project.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid);
// create a new `Tests` group so all added test files are nice and tidy in
// the Xcode project
const group = project.addPbxGroup([], 'Tests');
const mainGroup = project.pbxGroupByName('CustomTemplate');
mainGroup.children.push({
value: group.uuid,
comment: group.pbxGroup.name
});
// add the actual test files
const testsDir = path.resolve(platformDir, 'Tests');
project.addSourceFile(path.join(testsDir, 'SimpleClickTest.m'), { target: target.uuid }, group.uuid);
done();
}
});
cli.on('build.pre.build', (builder, done) => {
const schemePath = path.join(
builder.buildDir,
builder.tiapp.name + '.xcodeproj',
'xcshareddata/xcschemes',
builder.sanitizedAppName() + '.xcscheme'
)
let content = fs.readFileSync(schemePath, 'utf-8');
const testPlan = `<TestPlans>
<TestPlanReference
reference = "container:../../platform/ios/Tests/uitests.xctestplan"
default = "YES">
</TestPlanReference>
</TestPlans>`;
// TODO: make sure to only do this for the <TestAction>
content = content.replace('<AdditionalOptions>\n </AdditionalOptions>', testPlan);
fs.writeFile(schemePath, content, done);
});
cli.on('build.ios.xcodebuild', {
pre: (data, done) => {
const buildDir = data.ctx.buildDir;
// generate UI test target files
const testTargetDir = path.resolve(buildDir, 'UITests');
if (!fs.existsSync(testTargetDir)) {
fs.mkdirSync(testTargetDir);
}
const plistContent = fs.readFileSync(path.join(platformDir, 'Tests', 'Info.plist'), 'utf-8');
fs.writeFileSync(path.join(testTargetDir, 'UITests-Info.plist'), plistContent);
const args = data.args[1];
args.splice(0, 1, 'build-for-testing');
done();
}
});
};
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
//
// SimpleClickTest.m
// test-app
//
// Created by Jan Vennemann on 15.04.20.
//
#import <XCTest/XCTest.h>
@interface SimpleClickTest : XCTestCase
@end
@implementation SimpleClickTest
- (void)setUp {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
self.continueAfterFailure = NO;
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
[[[XCUIApplication alloc] init] launch];
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testExample {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
XCUIApplication *app = [[XCUIApplication alloc] init];
[app.staticTexts[@"Press Me!"] tap];
XCTAssert(app.staticTexts[@"Button pressed"].exists);
}
@end
{
"configurations" : [
{
"id" : "6C3DD310-DBCD-4583-A0F8-E553FCC84BCC",
"name" : "Configuration 1",
"options" : {
}
}
],
"defaultOptions" : {
},
"testTargets" : [
{
"target" : {
"containerPath" : "container:<project-name>.xcodeproj",
"identifier" : "000000000000000000000006",
"name" : "UITests"
}
}
],
"version" : 1
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment