├── test ├── mocha.opts └── xctest.test.js ├── index.js ├── .eslintignore ├── lib ├── logger.js ├── xctestwd.js ├── proxy.js ├── helper.js └── xctest-client.js ├── XCTestWD ├── libxml2 │ └── libxml2-fuzi.h ├── XCTestWD │ ├── Server │ │ ├── Modules │ │ │ ├── XCTestWDErrors.swift │ │ │ ├── Utils │ │ │ │ ├── XCTestWDApplicationTree.swift │ │ │ │ └── XCTestWDAccessibility.swift │ │ │ ├── Extensions │ │ │ │ ├── XCTestWDMathUtils.swift │ │ │ │ ├── XCTestWDFindElementUtils.swift │ │ │ │ └── XCTestWDXPath.swift │ │ │ ├── XCTestWDReponse.swift │ │ │ ├── XCTestWDAlert.swift │ │ │ ├── XCTestWDStatus.swift │ │ │ ├── XCUIElementTypeTransformer.swift │ │ │ └── XCTestWDSession.swift │ │ ├── XCTestWDDispatch.swift │ │ ├── XCTestWDFailureProofTestCase.swift │ │ ├── XCTestWDController.swift │ │ ├── Controllers │ │ │ ├── XCTestWDScreenshotController.swift │ │ │ ├── XCTestWDWindowController.swift │ │ │ ├── XCTestWDTitleController.swift │ │ │ ├── XCTestWDUrlController.swift │ │ │ ├── XCTestWDSourceController.swift │ │ │ ├── XCTestWDAlertController.swift │ │ │ └── XCTestWDSessionController.swift │ │ └── XCTestWDServer.swift │ ├── PrivateHeaders │ │ ├── XCDebugLogDelegate-Protocol.h │ │ ├── XCTestDaemonsProxy.h │ │ ├── Macaca │ │ │ ├── XCTestWDApplication.h │ │ │ ├── NSPredicate+XCTestWD.h │ │ │ ├── XCTestXCodeCompatibility.h │ │ │ ├── XCTestXCAXClientProxy.h │ │ │ ├── XCTestWDImplementationFailureHoldingProxy.h │ │ │ ├── XCTestWDImplementationFailureHoldingProxy.m │ │ │ ├── XCTestXCodeCompatibility.m │ │ │ ├── XCTestWDApplication.m │ │ │ ├── XCTestXCAXClientProxy.m │ │ │ └── NSPredicate+XCTestWD.m │ │ ├── XCTestManager_TestsInterface-Protocol.h │ │ ├── XCTestDriverInterface-Protocol.h │ │ ├── CDStructures.h │ │ ├── XCUIDevice.h │ │ ├── XCDeviceProtocols.h │ │ ├── XCTElementSetTransformer-Protocol.h │ │ ├── XCTestDaemonsProxy.m │ │ ├── XCRuntimeUtils.h │ │ ├── XCUICoordinate.h │ │ ├── XCTestPrivateSymbols.h │ │ ├── XCRuntimeUtils.m │ │ ├── XCTestDriver.h │ │ ├── XCUIElement.h │ │ ├── XCTestPrivateSymbols.m │ │ ├── XCAXClient_iOS.h │ │ ├── XCUIApplication.h │ │ ├── XCTestManager_IDEInterface-Protocol.h │ │ ├── XCUIElementQuery.h │ │ ├── XCTestManager_ManagerInterface-Protocol.h │ │ ├── _XCTestCaseImplementation.h │ │ ├── XCTRunnerDaemonSession.h │ │ ├── XCElementSnapshot.h │ │ └── XCTestCase.h │ ├── Info.plist │ └── XCTestWD.h ├── XCTestWDUnitTest │ ├── ControllerTests │ │ ├── XCTestWDWindowControllerTests.swift │ │ ├── XCTestWDTitleControllerTests.swift │ │ ├── XCTestWDScreenShotControllerTest.swift │ │ ├── XCTestWDUrlControllerTests.swift │ │ └── XCTestWDSourceControllerTests.swift │ ├── Info.plist │ ├── XCTestWDUnitTestBase.swift │ └── XCTestWDSessionTest.swift ├── XCTestWDUITests │ ├── Info.plist │ └── XCTestWDRunner.swift ├── XCTestWD-Info.plist ├── XCTestWD.xcodeproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── XCTestWDUnitTest.xcscheme │ │ ├── XCTestWD.xcscheme │ │ ├── XCTestWDAggregation.xcscheme │ │ └── XCTestWDUITests.xcscheme └── curlTests.sh ├── Cartfile ├── CONTRIBUTING.md ├── scripts ├── integration-testing.sh ├── aggregate.sh └── install.js ├── .npmignore ├── .gitignore ├── carthage.sh ├── LICENSE ├── package.json ├── .circleci └── config.yml ├── .eslintrc └── README.md /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/xctest-client'); 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | **/node_modules 3 | **/dist 4 | **/assets 5 | **/build 6 | **/test 7 | **/coverage 8 | **/Carthage 9 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var logger = require('xlogger'); 4 | 5 | module.exports = logger.Logger({ 6 | closeFile: true 7 | }); 8 | -------------------------------------------------------------------------------- /XCTestWD/libxml2/libxml2-fuzi.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | #import 6 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "httpswift/swifter" == 1.5.0 2 | github "SwiftyJSON/SwiftyJSON" == 5.0.1 3 | github "cezheng/Fuzi" == 3.1.3 4 | github "tadija/AEXML" == 4.6.1 5 | github "CocoaLumberjack/CocoaLumberjack" == 3.7.2 6 | github "Quick/Nimble" == 9.2.1 7 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/XCTestWDErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDErrors.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 11/5/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum XCTestWDRoutingError: Error { 12 | case noSuchUsingMethod 13 | } 14 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCDebugLogDelegate-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSString; 8 | 9 | @protocol XCDebugLogDelegate 10 | - (void)logDebugMessage:(NSString *)arg1; 11 | @end 12 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestDaemonsProxy.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @protocol XCTestManager_ManagerInterface; 5 | 6 | /** 7 | Temporary class used to abstract interactions with TestManager daemon between Xcode 8.2.1 and Xcode 8.3-beta 8 | */ 9 | @interface XCTestDaemonsProxy : NSObject 10 | 11 | + (id)testRunnerProxy; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestWDApplication.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDApplication.h 3 | // XCTestWDUITests 4 | // 5 | // Created by zhaoy on 24/9/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "XCUIApplication.h" 11 | 12 | @interface XCTestWDApplication : NSObject 13 | 14 | + (XCUIApplication*)activeApplication; 15 | 16 | + (XCUIApplication*)createByPID:(pid_t)pid; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to XCTestWD 2 | 3 | We love pull requests from everyone. 4 | 5 | ## Link Global To Local 6 | 7 | ```bash 8 | $ cd path/to/macaca-ios 9 | $ npm link path/to/XCTestWD 10 | # now project XCTestWD is linked to macaca-ios 11 | ``` 12 | 13 | ## Run with XCode 14 | 15 | ```bash 16 | $ open ./XCTestWD/XCTestWD.xcodeproj 17 | # run test(command + u) in XCTestWDUITests schema 18 | # ./XCTestWD/curlTests.sh is some useful restful testing scripts 19 | ``` 20 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestManager_TestsInterface-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSData, NSString; 8 | 9 | @protocol XCTestManager_TestsInterface 10 | - (void)_XCT_receivedAccessibilityNotification:(int)arg1 withPayload:(NSData *)arg2; 11 | - (void)_XCT_applicationWithBundleID:(NSString *)arg1 didUpdatePID:(int)arg2 andState:(unsigned long long)arg3; 12 | @end 13 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/NSPredicate+XCTestWD.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSPredicate+XCTestWD.h 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 18/8/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSPredicate (XCTestWD) 12 | 13 | // utility method for providing xctestwd predicates 14 | + (instancetype)xctestWDPredicateWithFormat:(NSString *)predicateFormat; 15 | 16 | + (instancetype)xctestWDformatSearchPredicate:(NSPredicate *)input; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /scripts/integration-testing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | brew install nvm > /dev/null 2>&1 4 | source $(brew --prefix nvm)/nvm.sh 5 | nvm install 8 6 | 7 | export XCTESTWD_PATH=`pwd`"/XCTestWD/XCTestWD.xcodeproj" 8 | 9 | echo process env XCTESTWD_PATH set to $XCTESTWD_PATH 10 | 11 | # temporary fix before fixing macaca-scripts 12 | brew install ios-webkit-debug-proxy > /dev/null 2>&1 13 | 14 | git clone https://github.com/macaca-sample/sample-nodejs.git --depth=1 15 | cd sample-nodejs 16 | 17 | npm i 18 | npm install macaca-ios -g 19 | npm run test:ios 20 | -------------------------------------------------------------------------------- /test/xctest.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const _ = require('macaca-utils'); 5 | 6 | const { XCTestWD } = require('..'); 7 | 8 | describe('project root path', () => { 9 | it('should be ok', () => { 10 | assert.equal(_.isExistedDir(XCTestWD.projectPath), true); 11 | }); 12 | 13 | it('env variable should be work', () => { 14 | delete require.cache[require.resolve('../lib/xctestwd')]; 15 | process.env.MACACA_XCTESTWD_ROOT_PATH = process.env.HOME; 16 | assert.equal(_.isExistedDir(require('../lib/xctestwd').projectPath), false); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/Utils/XCTestWDApplicationTree.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDApplicationTree.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 5/5/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | extension XCUIApplication { 13 | 14 | func mainWindowSnapshot() -> XCElementSnapshot? { 15 | let mainWindows = (self.fb_lastSnapshot()).descendantsByFiltering { (snapshot) -> Bool in 16 | return snapshot?.isMainWindow ?? false 17 | } 18 | return mainWindows?.last 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | DerivedData 17 | .idea/ 18 | # Normal 19 | .project 20 | .settings 21 | node_modules/ 22 | logs/ 23 | Cartfile.resolved 24 | UserInterfaceState.xcuserstate 25 | XCTestWD/build 26 | node_modules 27 | .travis.yml 28 | .jshintrc 29 | .jshintignore 30 | coverage/ 31 | .nyc_output 32 | logs/ 33 | package/ 34 | *.un~ 35 | *.tgz 36 | Carthage/ 37 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestDriverInterface-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSNumber; 8 | 9 | @protocol XCTestDriverInterface 10 | - (id)_IDE_processWithToken:(NSNumber *)arg1 exitedWithStatus:(NSNumber *)arg2; 11 | - (id)_IDE_stopTrackingProcessWithToken:(NSNumber *)arg1; 12 | - (id)_IDE_processWithBundleID:(NSString *)arg1 path:(NSString *)arg2 pid:(NSNumber *)arg3 crashedUnderSymbol:(NSString *)arg4; 13 | - (id)_IDE_startExecutingTestPlanWithProtocolVersion:(NSNumber *)arg1; 14 | @end 15 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/ControllerTests/XCTestWDWindowControllerTests.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // XCTestWDWindowControllerTests.swift 4 | // XCTestWDUnitTest 5 | // 6 | // Created by SamuelZhaoY on 2/4/18. 7 | // Copyright © 2018 XCTestWD. All rights reserved. 8 | // 9 | 10 | import XCTest 11 | import Nimble 12 | @testable import XCTestWD 13 | import Swifter 14 | 15 | class XCTestWDWindowControllerTests: XCTestWDUnitTestBase { 16 | 17 | func testWindowSize() { 18 | let request = Swifter.HttpRequest.init() 19 | let response = XCTestWDWindowController.getWindowSize(request: request) 20 | response.shouldBeSuccessful() 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lib/xctestwd.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | 5 | const xctestRootPath = process.env.MACACA_XCTESTWD_ROOT_PATH || path.join(__dirname, '..'); 6 | console.log('process.env.MACACA_XCTESTWD_ROOT_PATH'); 7 | console.log(process.env.MACACA_XCTESTWD_ROOT_PATH); 8 | 9 | exports.SERVER_URL_REG = /XCTestWDSetup->(.*)<-XCTestWDSetup/; 10 | exports.schemeName = 'XCTestWDUITests'; 11 | exports.projectPath = path.join(xctestRootPath, 'XCTestWD', 'XCTestWD.xcodeproj'); 12 | exports.version = require('../package').version; 13 | exports.BUNDLE_ID = 'XCTestWD.XCTestWD'; 14 | exports.simulatorLogFlag = 'IDETestOperationsObserverDebug: Writing diagnostic log for test session to:'; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | project.pbxproj 19 | .idea/ 20 | # Normal 21 | .project 22 | .settings 23 | node_modules/ 24 | logs/ 25 | Carthage/ 26 | Cartfile.resolved 27 | UserInterfaceState.xcuserstate 28 | .*.sw[a-z] 29 | .sw? 30 | *.un~ 31 | .DS_Store 32 | node_modules 33 | npm-debug.log 34 | .idea/* 35 | Thumbs.db 36 | coverage/ 37 | .project 38 | logs/ 39 | package/ 40 | Frameworks/ 41 | package-lock.json 42 | *.tgz 43 | .nyc_output -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/CDStructures.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #pragma mark Blocks 8 | 9 | typedef void (^CDUnknownBlockType)(void); // return type and parameters are unknown 10 | 11 | typedef struct { 12 | unsigned int _field1; 13 | unsigned int _field2; 14 | unsigned int _field3; 15 | unsigned int _field4; 16 | unsigned int _field5; 17 | unsigned int _field6; 18 | unsigned int _field7; 19 | } CDStruct_a561fd19; 20 | 21 | typedef struct { 22 | unsigned short _field1; 23 | unsigned short _field2; 24 | unsigned short _field3[1]; 25 | } CDStruct_27a325c0; 26 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/ControllerTests/XCTestWDTitleControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDTitleControllerTests.swift 3 | // XCTestWDUnitTest 4 | // 5 | // Created by SamuelZhaoY on 2/4/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Nimble 11 | @testable import XCTestWD 12 | import Swifter 13 | 14 | class XCTestWDTitleControllerTests: XCTestWDUnitTestBase { 15 | 16 | func testTitleRetrieve() { 17 | let request = Swifter.HttpRequest.init() 18 | let response = XCTestWDTitleController.title(request: request) 19 | let contentJSON = XCTestWDUnitTestBase.getResponseData(response) 20 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestXCodeCompatibility.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface XCUIElement (XCTestWD) 6 | 7 | - (void)fb_nativeResolve; 8 | 9 | - (XCElementSnapshot *)fb_lastSnapshot; 10 | 11 | @end 12 | 13 | @interface XCUIElementQuery (XCTestWD) 14 | 15 | /* Performs short-circuit UI tree traversion in iOS 11+ to get the first element matched by the query. Equals to nil if no matching elements are found */ 16 | @property(nullable, readonly) XCUIElement *fb_firstMatch; 17 | 18 | /** 19 | Retrieves the snapshot for the given element 20 | 21 | @returns The resolved snapshot 22 | */ 23 | - (XCElementSnapshot *)fb_elementSnapshotForDebugDescription; 24 | 25 | @end 26 | 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCUIDevice.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCUIDevice.h 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 5/4/19. 6 | // Copyright © 2019 XCTestWD. All rights reserved. 7 | // 8 | 9 | #ifndef XCUIDevice_h 10 | #define XCUIDevice_h 11 | 12 | @interface XCUIDevice () 13 | 14 | // Since Xcode 10.2 15 | @property (readonly) id accessibilityInterface; // implements XCUIAccessibilityInterface 16 | @property (readonly) id eventSynthesizer; // implements XCUIEventSynthesizing 17 | 18 | - (void)pressLockButton; 19 | - (void)holdHomeButtonForDuration:(double)arg1; 20 | - (void)_silentPressButton:(long long)arg1; 21 | - (void)_dispatchEventWithPage:(unsigned int)arg1 usage:(unsigned int)arg2 duration:(double)arg3; 22 | 23 | @end 24 | 25 | 26 | #endif /* XCUIDevice_h */ 27 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCDeviceProtocols.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCDeviceProtocols.h 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 5/4/19. 6 | // Copyright © 2019 XCTestWD. All rights reserved. 7 | // 8 | 9 | #ifndef XCDeviceProtocols_h 10 | #define XCDeviceProtocols_h 11 | 12 | @protocol XCUIIOSDevice 13 | @property(nonatomic) long long orientation; 14 | - (void)pressLockButton; 15 | - (void)holdHomeButtonForDuration:(double)arg1; 16 | - (void)pressButton:(long long)arg1; 17 | @end 18 | 19 | @protocol XCUIIPhoneOSDevice 20 | @property(readonly) XCUISiriService *siriService; 21 | @property(readonly) _Bool isSimulatorDevice; 22 | - (_Bool)performDeviceEvent:(XCDeviceEvent *)arg1 error:(id *)arg2; 23 | @end 24 | 25 | #endif /* XCDeviceProtocols_h */ 26 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestXCAXClientProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestXCAXClientProxy.h 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 5/4/19. 6 | // Copyright © 2019 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "XCElementSnapshot.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface XCTestXCAXClientProxy : NSObject 16 | 17 | + (instancetype)sharedClient; 18 | 19 | - (NSArray *)activeApplications; 20 | 21 | - (id)systemApplication; 22 | 23 | - (NSDictionary *)attributesForElement:(id )element 24 | attributes:(NSArray *)attributes; 25 | 26 | - (XCUIApplication *)monitoredApplicationWithProcessIdentifier:(int)pid; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/ControllerTests/XCTestWDScreenShotControllerTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDScreenShotControllerTest.swift 3 | // XCTestWDUnitTest 4 | // 5 | // Created by SamuelZhaoY on 6/5/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Nimble 11 | @testable import XCTestWD 12 | import Swifter 13 | 14 | class XCTestWDScreenShotControllerTest: XCTestWDUnitTestBase { 15 | 16 | func testScreenShotRetrieve() { 17 | let request = Swifter.HttpRequest.init() 18 | let response = XCTestWDScreenshotController.getScreenshot(request: request) 19 | let contentJSON = XCTestWDUnitTestBase.getResponseData(response) 20 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIFileSharingEnabled 6 | 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTElementSetTransformer-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit) (Debug version compiled Jun 9 2015 22:53:21). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2014 by Steve Nygard. 5 | // 6 | 7 | @class NSOrderedSet, NSSet, NSString, XCElementSnapshot; 8 | @protocol XCTMatchingElementIterator; 9 | 10 | @protocol XCTElementSetTransformer 11 | @property BOOL stopsOnFirstMatch; 12 | @property(readonly) BOOL supportsAttributeKeyPathAnalysis; 13 | @property(copy) NSString *transformationDescription; 14 | @property(readonly) BOOL supportsRemoteEvaluation; 15 | - (NSSet *)requiredKeyPathsOrError:(id *)arg1; 16 | - (id )iteratorForInput:(XCElementSnapshot *)arg1; 17 | - (NSOrderedSet *)transform:(NSOrderedSet *)arg1 relatedElements:(id *)arg2; 18 | @end 19 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestWDImplementationFailureHoldingProxy.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDImplementationFailureHoldingProxy.h 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 3/10/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class _XCTestCaseImplementation; 14 | /** 15 | Class that can be used to proxy existing _XCTestCaseImplementation and 16 | prevent currently running test from being terminated on any XCTest failure 17 | */ 18 | @interface XCTestWDImplementationFailureHoldingProxy : NSObject 19 | 20 | /** 21 | Constructor for given existing _XCTestCaseImplementation instance 22 | */ 23 | + (_XCTestCaseImplementation *)proxyWithXCTestCaseImplementation:(_XCTestCaseImplementation *)internalImplementation; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestDaemonsProxy.m: -------------------------------------------------------------------------------- 1 | 2 | #import "XCTestDaemonsProxy.h" 3 | #import "XCTestDriver.h" 4 | #import "XCTRunnerDaemonSession.h" 5 | #import 6 | 7 | @implementation XCTestDaemonsProxy 8 | 9 | + (id)testRunnerProxy 10 | { 11 | static id proxy = nil; 12 | static dispatch_once_t onceToken; 13 | dispatch_once(&onceToken, ^{ 14 | if ([[XCTestDriver sharedTestDriver] respondsToSelector:@selector(managerProxy)]) { 15 | proxy = [XCTestDriver sharedTestDriver].managerProxy; 16 | return; 17 | } 18 | Class runnerClass = objc_lookUpClass("XCTRunnerDaemonSession"); 19 | proxy = ((XCTRunnerDaemonSession *)[runnerClass sharedSession]).daemonProxy; 20 | }); 21 | NSAssert(proxy != NULL, @"Could not determin testRunnerProxy", proxy); 22 | return proxy; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/XCTestWDDispatch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDDispatch.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 25/4/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | 12 | //MARK: synchronous execution on main 13 | func SyncOnMain(_ executionBlock:(()->(HttpResponse))!) -> HttpResponse { 14 | var response: HttpResponse = HttpResponse.internalServerError 15 | DispatchQueue.main.sync { 16 | response = executionBlock() 17 | } 18 | return response 19 | } 20 | 21 | func RouteOnMain(_ routingCall:@escaping RoutingCall) -> RoutingCall { 22 | return { (request: HttpRequest) -> HttpResponse in 23 | var response:HttpResponse = HttpResponse.internalServerError 24 | DispatchQueue.main.sync { 25 | response = routingCall(request) 26 | } 27 | return response 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCRuntimeUtils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | Returns array of classes that conforms to given protocol 16 | */ 17 | NSArray *FBClassesThatConformsToProtocol(Protocol *protocol); 18 | 19 | /** 20 | Method used to retrieve pointer for given symbol 'name' from given 'binary' 21 | 22 | @param binary path to binary we want to retrieve symbols pointer from 23 | @param name name of the symbol 24 | @return pointer to symbol 25 | */ 26 | void *FBRetrieveSymbolFromBinary(const char *binary, const char *name); 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/XCTestWDFailureProofTestCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDFailureProofTestCase.swift 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 3/10/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | 12 | open class XCTestWDFailureProofTest : XCTestCase 13 | { 14 | override open func setUp() { 15 | super.setUp() 16 | continueAfterFailure = true; 17 | if(self.responds(to: #selector(setter: internalImplementation))){ 18 | internalImplementation = XCTestWDImplementationFailureHoldingProxy.proxy(with: self.internalImplementation) 19 | } else { 20 | shouldSetShouldHaltWhenReceivesControl = false 21 | shouldHaltWhenReceivesControl = false 22 | } 23 | } 24 | 25 | override open func recordFailure(withDescription description: String, inFile filePath: String, atLine lineNumber: Int, expected: Bool) { 26 | print("catching internal failure: \(description) in file: \(filePath) at line: \(lineNumber)") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /carthage.sh: -------------------------------------------------------------------------------- 1 | # carthage.sh 2 | # Usage example: ./carthage.sh build --platform iOS 3 | 4 | set -euo pipefail 5 | 6 | xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX) 7 | trap 'rm -f "$xcconfig"' INT TERM HUP EXIT 8 | 9 | # For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise 10 | # the build will fail on lipo due to duplicate architectures. 11 | 12 | CURRENT_XCODE_VERSION=$(xcodebuild -version | grep "Build version" | cut -d' ' -f3) 13 | echo "EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1300__BUILD_$CURRENT_XCODE_VERSION = arm64 arm64e armv7 armv7s armv6 armv8" >> $xcconfig 14 | 15 | echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1300 = $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1300__BUILD_$(XCODE_PRODUCT_BUILD_VERSION))' >> $xcconfig 16 | echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig 17 | 18 | export XCODE_XCCONFIG_FILE="$xcconfig" 19 | carthage "$@" -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/Extensions/XCTestWDMathUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDMathUtils.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 5/5/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MathUtils { 12 | 13 | static func adjustDimensionsForApplication(_ actualSize:CGSize , _ orientation:UIDeviceOrientation) -> CGSize { 14 | if (orientation == UIDeviceOrientation.landscapeLeft || orientation == UIDeviceOrientation.landscapeRight) { 15 | /* 16 | There is an XCTest bug that application.frame property returns exchanged dimensions for landscape mode. 17 | This verification is just to make sure the bug is still there (since height is never greater than width in landscape) 18 | and to make it still working properly after XCTest itself starts to respect landscape mode. 19 | */ 20 | if (actualSize.height > actualSize.width) { 21 | return CGSize(width:actualSize.height, height:actualSize.width) 22 | } 23 | } 24 | return actualSize; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2017 Alibaba Group Holding Limited and other contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUITests/XCTestWDRunner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // testUITests.swift 3 | // testUITests 4 | // 5 | // Created by xdf on 14/04/2017. 6 | // Copyright © 2017 xdf. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Swifter 11 | import XCTestWD 12 | 13 | public class XCTextWDRunner: XCTestWDFailureProofTest { 14 | var server: XCTestWDServer? 15 | override public func setUp() { 16 | super.setUp() 17 | NotificationCenter.default.addObserver(self, 18 | selector: #selector(terminate(notification:)), 19 | name: NSNotification.Name(rawValue: "XCTestWDSessionShutDown"), 20 | object: nil) 21 | } 22 | 23 | override public func tearDown() { 24 | super.tearDown() 25 | } 26 | 27 | func testRunner() { 28 | self.server = XCTestWDServer() 29 | self.server?.startServer() 30 | } 31 | 32 | @objc func terminate(notification: NSNotification) { 33 | self.server?.stopServer(); 34 | NSLog("XCTestWDTearDown->Session Reset") 35 | assert(false, "") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/XCTestWDController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCommandHandler.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Swifter 10 | 11 | class RequestRoute: Hashable, Equatable { 12 | 13 | internal var path:String! 14 | internal var verb:String! 15 | internal var requiresSession:Bool 16 | 17 | init(_ path:String , _ verb:String = "GET", _ requiresSession:Bool = true) { 18 | self.path = path 19 | self.verb = verb 20 | self.requiresSession = requiresSession 21 | } 22 | 23 | public var hashValue: Int { 24 | get { 25 | return "\(path)_\(verb)_\(requiresSession)".hashValue 26 | } 27 | } 28 | 29 | public static func ==(lhs: RequestRoute, rhs: RequestRoute) -> Bool { 30 | return lhs.path == rhs.path && lhs.verb == rhs.verb && lhs.requiresSession == rhs.requiresSession 31 | } 32 | } 33 | 34 | typealias RoutingCall = ((Swifter.HttpRequest) -> Swifter.HttpResponse) 35 | 36 | internal protocol Controller { 37 | 38 | static func routes() -> [(RequestRoute, RoutingCall)] 39 | 40 | static func shouldRegisterAutomatically() -> Bool 41 | 42 | } 43 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestWDImplementationFailureHoldingProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDImplementationFailureHoldingProxy.m 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 3/10/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import "XCTestWDImplementationFailureHoldingProxy.h" 10 | #import "_XCTestCaseImplementation.h" 11 | 12 | @interface XCTestWDImplementationFailureHoldingProxy() 13 | 14 | @property (nonatomic, strong) _XCTestCaseImplementation *internalImplementation; 15 | 16 | @end 17 | 18 | @implementation XCTestWDImplementationFailureHoldingProxy 19 | 20 | + (_XCTestCaseImplementation *)proxyWithXCTestCaseImplementation:(_XCTestCaseImplementation *)internalImplementation 21 | { 22 | XCTestWDImplementationFailureHoldingProxy *proxy = [super alloc]; 23 | proxy.internalImplementation = internalImplementation; 24 | return (_XCTestCaseImplementation *)proxy; 25 | } 26 | 27 | - (id)forwardingTargetForSelector:(SEL)aSelector 28 | { 29 | return self.internalImplementation; 30 | } 31 | 32 | // This will prevent test from quiting on app crash or any other test failure 33 | - (BOOL)shouldHaltWhenReceivesControl 34 | { 35 | return NO; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCUICoordinate.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | #import 9 | 10 | @class XCUIElement; 11 | 12 | #if !TARGET_OS_TV 13 | @interface XCUICoordinate () 14 | { 15 | XCUIElement *_element; 16 | XCUICoordinate *_coordinate; 17 | CGVector _normalizedOffset; 18 | CGVector _pointsOffset; 19 | } 20 | 21 | @property(readonly) CGVector pointsOffset; // @synthesize pointsOffset=_pointsOffset; 22 | @property(readonly) CGVector normalizedOffset; // @synthesize normalizedOffset=_normalizedOffset; 23 | @property(readonly) XCUICoordinate *coordinate; // @synthesize coordinate=_coordinate; 24 | @property(readonly) XCUIElement *element; // @synthesize element=_element; 25 | 26 | - (id)initWithCoordinate:(id)arg1 pointsOffset:(CGVector)arg2; 27 | - (id)initWithElement:(id)arg1 normalizedOffset:(CGVector)arg2; 28 | - (id)init; 29 | 30 | - (void)pressForDuration:(double)arg1 thenDragToCoordinate:(id)arg2; 31 | - (void)pressForDuration:(double)arg1; 32 | - (void)doubleTap; 33 | - (void)tap; 34 | 35 | @end 36 | #endif 37 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestPrivateSymbols.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @protocol XCDebugLogDelegate; 13 | 14 | /*! Accessibility identifier for is visible attribute */ 15 | extern NSNumber *XCAXAIsVisibleAttribute; 16 | 17 | /*! Accessibility identifier for is accessible attribute */ 18 | extern NSNumber *XCAXAIsElementAttribute; 19 | 20 | /*! Getter for XCTest logger */ 21 | extern id (*XCDebugLogger)(void); 22 | 23 | /*! Setter for XCTest logger */ 24 | extern void (*XCSetDebugLogger)(id ); 25 | 26 | /** 27 | Method used to retrieve pointer for given symbol 'name' from given 'binary' 28 | 29 | @param name name of the symbol 30 | @return pointer to symbol 31 | */ 32 | void *RetrieveXCTestSymbol(const char *name); 33 | 34 | /*! Static constructor that will retrieve XCTest private symbols */ 35 | __attribute__((constructor)) void LoadXCTestSymbols(void); 36 | 37 | int portNumber(void); 38 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Controllers/XCTestWDScreenshotController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestAlertViewCommand.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import SwiftyJSON 12 | 13 | internal class XCTestWDScreenshotController: Controller { 14 | 15 | //MARK: Controller - Protocol 16 | static func routes() -> [(RequestRoute, RoutingCall)] { 17 | return [(RequestRoute("/wd/hub/screenshot", "get"), getScreenshot), 18 | (RequestRoute("/wd/hub/session/:sessionId/screenshot", "get"), getScreenshot)] 19 | } 20 | 21 | static func shouldRegisterAutomatically() -> Bool { 22 | return false 23 | } 24 | 25 | //MARK: Routing Logic Specification 26 | internal static func getScreenshot(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 27 | var base64String:String! 28 | let xcScreen:AnyClass? = NSClassFromString("XCUIScreen") 29 | if xcScreen != nil { 30 | let data = xcScreen?.value(forKeyPath: "mainScreen.screenshot.PNGRepresentation") as? NSData 31 | base64String = ((data?.base64EncodedString()))! 32 | } 33 | 34 | return XCTestWDResponse.response(session: request.session, value: JSON(base64String!)) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Controllers/XCTestWDWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestAlertViewCommand.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import SwiftyJSON 12 | 13 | internal class XCTestWDWindowController: Controller { 14 | 15 | //MARK: Controller - Protocol 16 | static func routes() -> [(RequestRoute, RoutingCall)] { 17 | return [(RequestRoute("/wd/hub/session/:sessionId/window/current/size", "get"), getWindowSize)] 18 | } 19 | 20 | static func shouldRegisterAutomatically() -> Bool { 21 | return false 22 | } 23 | 24 | //MARK: Routing Logic Specification 25 | internal static func getWindowSize(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 26 | let application = XCTestWDSessionManager.singleton.checkDefaultSession().application 27 | let frame = application?.wdFrame() 28 | let screenSize = MathUtils.adjustDimensionsForApplication(frame!.size, UIDeviceOrientation.init(rawValue:(application?.interfaceOrientation.rawValue)!)!) 29 | 30 | return XCTestWDResponse.response(session: nil, value: JSON(["width":screenSize.width,"height":screenSize.height])) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xctestwd", 3 | "version": "1.4.34", 4 | "description": "Swift implementation of WebDriver server for iOS that runs on Simulator/iOS devices.", 5 | "keywords": [ 6 | "iOS", 7 | "xctest" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/macacajs/xctestwd.git" 12 | }, 13 | "main": "index.js", 14 | "dependencies": { 15 | "ios-utils": "1", 16 | "macaca-doctor": "2", 17 | "macaca-utils": "^1.0.0", 18 | "request": "~2.69.0", 19 | "shelljs": "^0.7.8", 20 | "webdriver-dfn-error-code": "~1.0.1", 21 | "xcode": "^0.8.9", 22 | "xctestwd-frameworks-13": "1", 23 | "xctestwd-frameworks-12dot5": "1", 24 | "xctestwd-frameworks-12": "1", 25 | "xctestwd-frameworks": "1", 26 | "xlogger": "~1.0.0" 27 | }, 28 | "devDependencies": { 29 | "eslint": "^4.13.0", 30 | "eslint-plugin-mocha": "^4.11.0", 31 | "git-contributor": "1", 32 | "husky": "^1.3.1", 33 | "mocha": "*", 34 | "nyc": "^13.3.0" 35 | }, 36 | "husky": { 37 | "hooks": { 38 | "pre-commit": "npm run lint" 39 | } 40 | }, 41 | "scripts": { 42 | "test": "nyc --reporter=lcov --reporter=text mocha", 43 | "lint": "eslint . --fix", 44 | "install": "node ./scripts/install.js", 45 | "contributor": "git-contributor" 46 | }, 47 | "site": "https://macacajs.github.io", 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestXCodeCompatibility.m: -------------------------------------------------------------------------------- 1 | #import "XCTestXCodeCompatibility.h" 2 | 3 | #import "XCUIElementQuery.h" 4 | 5 | @implementation XCUIElement (XCTestWD) 6 | 7 | - (void)fb_nativeResolve 8 | { 9 | if ([self respondsToSelector:@selector(resolve)]) { 10 | [self resolve]; 11 | return; 12 | } 13 | if ([self respondsToSelector:@selector(resolveOrRaiseTestFailure)]) { 14 | @try { 15 | [self resolveOrRaiseTestFailure]; 16 | } @catch (NSException *e) { 17 | NSLog(@"Failure while resolving '%@': %@", self.description, e.reason); 18 | } 19 | return; 20 | } 21 | } 22 | 23 | - (XCElementSnapshot *)fb_lastSnapshot 24 | { 25 | return [self.query fb_elementSnapshotForDebugDescription]; 26 | } 27 | 28 | @end 29 | 30 | @implementation XCUIElementQuery (XCTestWD) 31 | 32 | - (XCUIElement *)fb_firstMatch 33 | { 34 | if (!self.element.exists) { 35 | return nil; 36 | } 37 | return self.allElementsBoundByAccessibilityElement.firstObject; 38 | } 39 | 40 | - (XCElementSnapshot *)fb_elementSnapshotForDebugDescription 41 | { 42 | if ([self respondsToSelector:@selector(elementSnapshotForDebugDescription)]) { 43 | return [self elementSnapshotForDebugDescription]; 44 | } 45 | if ([self respondsToSelector:@selector(elementSnapshotForDebugDescriptionWithNoMatchesMessage:)]) { 46 | return [self elementSnapshotForDebugDescriptionWithNoMatchesMessage:nil]; 47 | } 48 | return nil; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCRuntimeUtils.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "XCRuntimeUtils.h" 11 | #include 12 | #import 13 | 14 | 15 | NSArray *FBClassesThatConformsToProtocol(Protocol *protocol) 16 | { 17 | Class *classes = NULL; 18 | NSMutableArray *collection = [NSMutableArray array]; 19 | int numClasses = objc_getClassList(NULL, 0); 20 | if (numClasses == 0 ) { 21 | return @[]; 22 | } 23 | 24 | classes = (__unsafe_unretained Class*)malloc(sizeof(Class) * numClasses); 25 | numClasses = objc_getClassList(classes, numClasses); 26 | for (int index = 0; index < numClasses; index++) { 27 | Class aClass = classes[index]; 28 | if (class_conformsToProtocol(aClass, protocol)) { 29 | [collection addObject:aClass]; 30 | } 31 | } 32 | 33 | free(classes); 34 | return collection.copy; 35 | } 36 | 37 | void *FBRetrieveSymbolFromBinary(const char *binary, const char *name) 38 | { 39 | void *handle = dlopen(binary, RTLD_LAZY); 40 | NSCAssert(handle, @"%s could not be opened", binary); 41 | void *pointer = dlsym(handle, name); 42 | NSCAssert(pointer, @"%s could not be located", name); 43 | return pointer; 44 | } 45 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/XCTestWD.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWD.h 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 22/02/2018. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for XCTestWD. 12 | FOUNDATION_EXPORT double XCTestWDVersionNumber; 13 | 14 | //! Project version string for XCTestWD. 15 | FOUNDATION_EXPORT const unsigned char XCTestWDVersionString[]; 16 | 17 | //! Export debug level 18 | #define LOG_LEVEL_DEF ddLogLevel 19 | 20 | // In this header, you should import all the public headers of your framework using statements like #import 21 | 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | #import 34 | #import 35 | #import 36 | #import 37 | #import 38 | #import 39 | #import 40 | #import 41 | #import 42 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # iOS CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/ios-migrating-from-1-2/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | 9 | # Specify the Xcode version to use 10 | macos: 11 | xcode: "11.1.0" 12 | 13 | steps: 14 | - checkout 15 | 16 | # Install CocoaPods 17 | - run: 18 | name: Carthage Update 19 | command: carthage update --platform iOS --configuration Debug 20 | 21 | # Build the app and run tests 22 | - run: 23 | name: Preparing Tests 24 | command: echo "preparing execution" 25 | environment: 26 | SCAN_DEVICE: iPhone 11 27 | 28 | - run: 29 | name: Run XCTestWD Unit Tests 30 | command: xcodebuild -project XCTestWD/XCTestWD.xcodeproj 31 | -scheme XCTestWDUnitTest 32 | -destination 'platform=iOS Simulator,name=iPhone 11' 33 | test 34 | 35 | - run: 36 | name: Run Macaca iOS Integration Tests 37 | command: ./scripts/integration-testing.sh 38 | 39 | # Collect XML test results data to show in the UI, 40 | # and save the same XML files under test-results folder 41 | # in the Artifacts tab 42 | - store_test_results: 43 | path: test_output/report.xml 44 | - store_artifacts: 45 | path: /tmp/test-results 46 | destination: scan-test-results 47 | - store_artifacts: 48 | path: ~/Library/Logs/scan 49 | destination: scan-logs 50 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestWDApplication.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDApplication.m 3 | // XCTestWDUITests 4 | // 5 | // Created by zhaoy on 24/9/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import "XCTestWDApplication.h" 10 | #import "XCUIApplication.h" 11 | #import "XCTestXCAXClientProxy.h" 12 | 13 | @implementation XCTestWDApplication 14 | 15 | + (XCUIApplication*)activeApplication 16 | { 17 | id activeApplicationElement = ((NSArray*)[[XCTestXCAXClientProxy sharedClient] activeApplications]).lastObject; 18 | 19 | if (!activeApplicationElement) { 20 | activeApplicationElement = ((XCTestXCAXClientProxy*)[XCTestXCAXClientProxy sharedClient]).systemApplication; 21 | } 22 | 23 | XCUIApplication* application = [XCTestWDApplication createByPID:[[activeApplicationElement valueForKey:@"processIdentifier"] intValue]]; 24 | 25 | if (application.state != XCUIApplicationStateRunningForeground) { 26 | application = [[XCUIApplication alloc] initPrivateWithPath:nil bundleID:@"com.apple.springboard"]; 27 | } 28 | 29 | [application query]; 30 | return application; 31 | } 32 | 33 | + (XCUIApplication*)createByPID:(pid_t)pid 34 | { 35 | if ([XCUIApplication respondsToSelector:@selector(appWithPID:)]) { 36 | return [XCUIApplication appWithPID:pid]; 37 | } 38 | 39 | if ([XCUIApplication respondsToSelector:@selector(applicationWithPID:)]) { 40 | return [XCUIApplication applicationWithPID:pid]; 41 | } 42 | 43 | return [[XCTestXCAXClientProxy sharedClient] monitoredApplicationWithProcessIdentifier:pid]; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/ControllerTests/XCTestWDUrlControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDUrlController.swift 3 | // XCTestWDUnitTest 4 | // 5 | // Created by SamuelZhaoY on 2/4/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Nimble 11 | @testable import XCTestWD 12 | import Swifter 13 | 14 | class XCTestWDUrlControllerTests: XCTestWDUnitTestBase { 15 | 16 | func testUrlController() { 17 | let request = Swifter.HttpRequest.init() 18 | let response = XCTestWDUrlController.url(request: request) 19 | response.shouldBeSuccessful() 20 | } 21 | 22 | func testGetUrlController() { 23 | let request = Swifter.HttpRequest.init() 24 | let response = XCTestWDUrlController.getUrl(request: request) 25 | response.shouldBeSuccessful() 26 | } 27 | 28 | func testForwardUrlController() { 29 | let request = Swifter.HttpRequest.init() 30 | let response = XCTestWDUrlController.forward(request: request) 31 | response.shouldBeSuccessful() 32 | } 33 | 34 | func testRefreshUrlController() { 35 | let request = Swifter.HttpRequest.init() 36 | let response = XCTestWDUrlController.refresh(request: request) 37 | response.shouldBeSuccessful() 38 | } 39 | 40 | func testBack() { 41 | let request = Swifter.HttpRequest.init() 42 | let response = XCTestWDUrlController.back(request: request) 43 | let contentJSON = XCTestWDUnitTestBase.getResponseData(response) 44 | expect(contentJSON["status"].int).to(equal(WDStatus.ElementIsNotSelectable.rawValue)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Controllers/XCTestWDTitleController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestAlertViewCommand.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import SwiftyJSON 12 | import CocoaLumberjackSwift 13 | 14 | internal class XCTestWDTitleController: Controller { 15 | 16 | //MARK: Controller - Protocol 17 | static func routes() -> [(RequestRoute, RoutingCall)] { 18 | return [(RequestRoute("/title", "get"), title), 19 | (RequestRoute("/wd/hub/session/:sessionId/title", "get"), title)] 20 | } 21 | 22 | static func shouldRegisterAutomatically() -> Bool { 23 | return false 24 | } 25 | 26 | //MARK: Routing Logic Specification 27 | internal static func title(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 28 | 29 | let session = XCTestWDSessionManager.singleton.checkDefaultSession() 30 | let application = session.application 31 | 32 | let elements = application?.descendants(matching: XCUIElement.ElementType.window).allElementsBoundByIndex 33 | 34 | if elements == nil || elements?.count == 0 { 35 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) title, elements and element count") 36 | return XCTestWDResponse.response(session: nil, error: WDStatus.ElementNotVisible) 37 | } 38 | 39 | let window = elements![0] 40 | let navBar = window.descendants(matching: XCUIElement.ElementType.navigationBar).allElementsBoundByIndex.first 41 | window.fb_nativeResolve() 42 | let digest = window.digest(windowName: navBar?.identifier == nil ? "" : (navBar?.identifier)!) 43 | return XCTestWDResponse.response(session: nil, value: JSON(digest as Any)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestDriver.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "CDStructures.h" 8 | #import "XCDebugLogDelegate-Protocol.h" 9 | #import "XCTestDriverInterface-Protocol.h" 10 | #import "XCTestManager_TestsInterface-Protocol.h" 11 | #import "XCTestManager_IDEInterface-Protocol.h" 12 | #import "XCTestManager_ManagerInterface-Protocol.h" 13 | 14 | @class DTXConnection, NSMutableArray, NSString, NSUUID, NSXPCConnection, XCTestConfiguration, XCTestSuite; 15 | 16 | @interface XCTestDriver : NSObject 17 | { 18 | XCTestConfiguration *_testConfiguration; 19 | NSObject *_queue; 20 | NSMutableArray *_debugMessageBuffer; 21 | int _debugMessageBufferOverflow; 22 | } 23 | @property int debugMessageBufferOverflow; // @synthesize debugMessageBufferOverflow=_debugMessageBufferOverflow; 24 | @property(retain) NSMutableArray *debugMessageBuffer; // @synthesize debugMessageBuffer=_debugMessageBuffer; 25 | @property(retain) NSObject *queue; // @synthesize queue=_queue; 26 | @property(readonly) XCTestConfiguration *testConfiguration; // @synthesize testConfiguration=_testConfiguration; 27 | 28 | + (instancetype)sharedTestDriver; 29 | 30 | - (void)runTestConfiguration:(id)arg1 completionHandler:(CDUnknownBlockType)arg2; 31 | - (void)runTestSuite:(id)arg1 completionHandler:(CDUnknownBlockType)arg2; 32 | - (void)reportStallOnMainThreadInTestCase:(id)arg1 method:(id)arg2 file:(id)arg3 line:(unsigned long long)arg4; 33 | - (BOOL)runTestsAndReturnError:(id *)arg1; 34 | - (id)_readyIDESession:(id *)arg1; 35 | - (int)_connectedSocketForIDESession:(id *)arg1; 36 | - (void)logDebugMessage:(id)arg1; 37 | - (id)initWithTestConfiguration:(id)arg1; 38 | 39 | // Removed with iOS 10.3 40 | @property(readonly) id managerProxy; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /scripts/aggregate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | # Sets the target folders and the final framework product. 6 | PROJECT_NAME=XCTestWD 7 | FRAMEWORK_CONFIG=Release 8 | BUILD_TARGET=XCTestWD 9 | 10 | # Install dir will be the final output to the framework. 11 | # The following line create it in the root folder of the current project. 12 | INSTALL_DIR="$SRCROOT/Frameworks/$BUILD_TARGET.framework" 13 | 14 | # Working dir will be deleted after the framework creation. 15 | WORK_DIR="$SRCROOT/build" 16 | DEVICE_DIR="$WORK_DIR/${FRAMEWORK_CONFIG}-iphoneos/$BUILD_TARGET.framework" 17 | SIMULATOR_DIR="$WORK_DIR/${FRAMEWORK_CONFIG}-iphonesimulator/$BUILD_TARGET.framework" 18 | rm -rf "$WORK_DIR" 19 | 20 | echo "Building device..." 21 | xcodebuild -configuration "$FRAMEWORK_CONFIG" -target "$BUILD_TARGET" -sdk iphoneos -project "$PROJECT_NAME.xcodeproj" > /dev/null 22 | 23 | echo "Building simulator..." 24 | xcodebuild -configuration "$FRAMEWORK_CONFIG" -target "$BUILD_TARGET" -sdk iphonesimulator -project "$PROJECT_NAME.xcodeproj" > /dev/null 25 | 26 | echo "Preparing directory..." 27 | rm -rf "$INSTALL_DIR" 28 | mkdir -p "$INSTALL_DIR/Headers" 29 | mkdir -p "$INSTALL_DIR/Modules/$BUILD_TARGET.swiftmodule" 30 | 31 | # Regulating Framework Deliverables 32 | echo "Migrating System Headers:" 33 | cp "$SIMULATOR_DIR/Headers/"*.h "$INSTALL_DIR/Headers/" 34 | 35 | echo "Mixing Mutli-Architecture Swift Modules:" 36 | cp "$SIMULATOR_DIR/Modules/module.modulemap" "$INSTALL_DIR/Modules/" 37 | cp "$SIMULATOR_DIR/Modules/$BUILD_TARGET.swiftmodule/"* "$INSTALL_DIR/Modules/$BUILD_TARGET.swiftmodule/" 38 | cp "$DEVICE_DIR/Modules/$BUILD_TARGET.swiftmodule/"* "$INSTALL_DIR/Modules/$BUILD_TARGET.swiftmodule/" 39 | cp "$SIMULATOR_DIR/Info.plist" "$INSTALL_DIR/" 40 | 41 | echo "Combine Fat File" 42 | lipo -create "$DEVICE_DIR/$BUILD_TARGET" "$SIMULATOR_DIR/$BUILD_TARGET" -output "${INSTALL_DIR}/${BUILD_TARGET}" 43 | 44 | # Clean Up Intermediate File 45 | # rm -rf "$WORK_DIR" 46 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/XCTestXCAXClientProxy.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestXCAXClientProxy.m 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 5/4/19. 6 | // Copyright © 2019 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import "XCTestXCAXClientProxy.h" 10 | 11 | #import "XCAXClient_iOS.h" 12 | #import "XCUIDevice.h" 13 | 14 | static id XCTestAXClient = nil; 15 | 16 | @implementation XCTestXCAXClientProxy 17 | 18 | + (instancetype)sharedClient 19 | { 20 | static XCTestXCAXClientProxy *instance = nil; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | instance = [[self alloc] init]; 24 | if ([XCAXClient_iOS.class respondsToSelector:@selector(sharedClient)]) { 25 | XCTestAXClient = [XCAXClient_iOS sharedClient]; 26 | } else { 27 | XCTestAXClient = [XCUIDevice.sharedDevice accessibilityInterface]; 28 | } 29 | }); 30 | return instance; 31 | } 32 | 33 | - (NSArray *)activeApplications 34 | { 35 | return [XCTestAXClient activeApplications]; 36 | } 37 | 38 | - (id)systemApplication 39 | { 40 | return [XCTestAXClient systemApplication]; 41 | } 42 | 43 | - (NSDictionary *)attributesForElement:(id )element 44 | attributes:(NSArray *)attributes 45 | { 46 | if ([XCTestAXClient respondsToSelector:@selector(attributesForElement:attributes:error:)]) { 47 | NSError *error = nil; 48 | NSDictionary* result = [XCTestAXClient attributesForElement:element 49 | attributes:attributes 50 | error:&error]; 51 | return result; 52 | } 53 | return [XCTestAXClient attributesForElement:element attributes:attributes]; 54 | } 55 | 56 | - (XCUIApplication *)monitoredApplicationWithProcessIdentifier:(int)pid 57 | { 58 | return [[XCTestAXClient applicationProcessTracker] monitoredApplicationWithProcessIdentifier:pid]; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/Extensions/XCTestWDFindElementUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDFindElementUtils.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 7/5/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class XCTestWDFindElementUtils { 12 | 13 | static func filterElement(usingText:String, withvalue:String, underElement:XCUIElement) throws -> XCUIElement? { 14 | return try filterElements(usingText:usingText, withValue:withvalue, underElement:underElement, returnAfterFirstMatch:true)?.first 15 | } 16 | 17 | 18 | // Routing for xpath, class name, name, id 19 | static func filterElements(usingText:String, withValue:String, underElement:XCUIElement, returnAfterFirstMatch:Bool) throws -> [XCUIElement]? { 20 | 21 | let isSearchByIdentifier = (usingText == "name" || usingText == "id" || usingText == "accessibility id") 22 | 23 | if usingText == "xpath" { 24 | return underElement.descendantsMatchingXPathQuery(xpathQuery: withValue, 25 | returnAfterFirstMatch: returnAfterFirstMatch) 26 | } else if usingText == "class name" { 27 | return underElement.descendantsMatchingClassName(className: withValue, 28 | returnAfterFirstMatch: returnAfterFirstMatch) 29 | } else if isSearchByIdentifier { 30 | return underElement.descendantsMatchingIdentifier(accessibilityId: withValue, 31 | returnAfterFirstMatch: returnAfterFirstMatch) 32 | } else if usingText == "predicate string" { 33 | let predicate = NSPredicate.xctestWDPredicate(withFormat: withValue) 34 | return underElement.descendantsMatching(Predicate: predicate!, returnAfterFirstMatch) 35 | } 36 | 37 | throw XCTestWDRoutingError.noSuchUsingMethod 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/XCTestWDUnitTestBase.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // XCTestWDUnitTestBase.swift 4 | // XCTestWDUnitTest 5 | // 6 | // Created by SamuelZhaoY on 1/4/18. 7 | // Copyright © 2018 XCTestWD. All rights reserved. 8 | // 9 | 10 | import XCTest 11 | import SwiftyJSON 12 | import Nimble 13 | @testable import XCTestWD 14 | import Swifter 15 | 16 | class XCTestWDUnitTestBase: XCTestCase { 17 | 18 | var springApplication: XCUIApplication? 19 | 20 | override func setUp() { 21 | super.setUp() 22 | continueAfterFailure = false 23 | XCTestWDSessionManager.singleton.clearAll() 24 | self.springApplication = XCTestWDApplication.activeApplication() 25 | XCUIDevice.shared.press(XCUIDevice.Button.home) 26 | sleep(2) 27 | } 28 | 29 | override func tearDown() { 30 | super.tearDown() 31 | XCUIDevice.shared.press(XCUIDevice.Button.home) 32 | XCUIDevice.shared.press(XCUIDevice.Button.home) 33 | XCTestWDSessionManager.singleton.clearAll() 34 | } 35 | 36 | static func getResponseData(_ response: Swifter.HttpResponse) -> JSON { 37 | switch response { 38 | case .ok(let body): 39 | switch body { 40 | case .text(let content): 41 | if let dataFromString = content.data(using: .utf8, allowLossyConversion: false) { 42 | return (try? JSON(data: dataFromString)) ?? JSON.init(stringLiteral: "") 43 | } else { 44 | break 45 | } 46 | case .html( _): 47 | return JSON(["status": 0]) 48 | default: 49 | break 50 | } 51 | default: 52 | break 53 | } 54 | 55 | return JSON.init("") 56 | } 57 | } 58 | 59 | extension HttpResponse { 60 | func shouldBeSuccessful() 61 | { 62 | let jsonContent = XCTestWDUnitTestBase.getResponseData(self) 63 | expect(jsonContent["status"]).to(equal(0)) 64 | expect(self.statusCode).to(equal(200)) 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD.xcodeproj/xcshareddata/xcschemes/XCTestWDUnitTest.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /XCTestWD/curlTests.sh: -------------------------------------------------------------------------------- 1 | 2 | export JSON_HEADER='-H "Content-Type:application/json"' 3 | export DEVICE_URL='http://127.0.0.1:8001' 4 | 5 | TagHead() { 6 | echo "\n#### $1 ####" 7 | } 8 | 9 | #Session: create session 10 | 11 | TagHead "Create Session" 12 | 13 | curl -X POST $JSON_HEADER \ 14 | -d "{\"desiredCapabilities\":{\"deviceName\":\"iPhone 8\",\"platformName\":\"iOS\", \"bundleId\":\"com.apple.mobilesafari\",\"autoAcceptAlerts\":false}}" \ 15 | $DEVICE_URL/wd/hub/session \ 16 | 17 | ### Read session as input for next stage testing 18 | 19 | echo "\n\ninput the sessionID generated:\n" 20 | read sessionID 21 | 22 | #Session: query sessions 23 | 24 | TagHead "Query Session" 25 | 26 | curl -X GET $JSON_HEADER \ 27 | $DEVICE_URL/wd/hub/sessions \ 28 | 29 | #Session: checkon source 30 | 31 | curl -X POST $JSON_HEADER \ 32 | -d "{\"using\":\"xpath\",\"value\":\"//XCUIElementTypeTextField[1]\"}" \ 33 | $DEVICE_URL/wd/hub/session/$sessionID/elements \ 34 | 35 | echo "\n\ninput the elementID generated:\n" 36 | read elementID 37 | 38 | curl -X POST $JSON_HEADER \ 39 | $DEVICE_URL/wd/hub/session/$sessionID/tap/$elementID \ 40 | 41 | TagHead "Check Source" 42 | 43 | curl -X GET $JSON_HEADER \ 44 | $DEVICE_URL/wd/hub/source \ 45 | 46 | TagHead "Press Home" 47 | 48 | curl -X POST $JSON_HEADER \ 49 | $DEVICE_URL/wd/hub/session/$sessionID/homeScreen \ 50 | 51 | TagHead "Title" 52 | 53 | curl -X GET $JSON_HEADER \ 54 | $DEVICE_URL/wd/hub/session/$sessionID/title \ 55 | 56 | TagHead "Check" 57 | 58 | curl -X GET $JSON_HEADER \ 59 | $DEVICE_URL/wd/hub/session/$sessionID/title \ 60 | 61 | TagHead "Trigger touch event by x & y" 62 | 63 | curl -X POST $JSON_HEADER \ 64 | -d "{\"using\":\"xpath\",\"value\":\"//*[@name="Messages"]\"}" \ 65 | $DEVICE_URL/wd/hub/session/$sessionID/elements \ 66 | 67 | curl -X POST $JSON_HEADER \ 68 | -d "{\"using\":\"predicate string\",\"value\":\"label CONTAINS[c] 'Messages'\"}" \ 69 | $DEVICE_URL/wd/hub/session/$sessionID/elements \ 70 | 71 | echo "\n\ninput the elementID generated:\n" 72 | read elementID 73 | 74 | curl -X POST $JSON_HEADER \ 75 | $DEVICE_URL/wd/hub/session/$sessionID/tap/$elementID \ 76 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Controllers/XCTestWDUrlController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestAlertViewCommand.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | 12 | internal class XCTestWDUrlController: Controller { 13 | 14 | //MARK: Controller - Protocol 15 | static func routes() -> [(RequestRoute, RoutingCall)] { 16 | return [(RequestRoute("/wd/hub/session/:sessionId/url", "post"), url), 17 | (RequestRoute("/wd/hub/session/:sessionId/url", "get"), getUrl), 18 | (RequestRoute("/wd/hub/session/:sessionId/forward", "post"), forward), 19 | (RequestRoute("/wd/hub/session/:sessionId/back", "post"), back), 20 | (RequestRoute("/wd/hub/session/:sessionId/refresh", "post"), refresh)] 21 | } 22 | 23 | static func shouldRegisterAutomatically() -> Bool { 24 | return false 25 | } 26 | 27 | //MARK: Routing Logic Specification 28 | internal static func url(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 29 | return HttpResponse.ok(.html("url")) 30 | } 31 | 32 | internal static func getUrl(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 33 | return HttpResponse.ok(.html("getUrl")) 34 | } 35 | 36 | internal static func forward(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 37 | return HttpResponse.ok(.html("forward")) 38 | } 39 | 40 | internal static func back(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 41 | let session = request.session ?? XCTestWDSessionManager.singleton.checkDefaultSession() 42 | let application = session.application 43 | if ((application?.navigationBars.buttons.count) ?? 0 > 0) { 44 | application?.navigationBars.buttons.element(boundBy: 0).tap() 45 | return XCTestWDResponse.response(session: nil, error: WDStatus.Success) 46 | } 47 | 48 | return XCTestWDResponse.response(session: nil, error: WDStatus.ElementIsNotSelectable) 49 | } 50 | 51 | internal static func refresh(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 52 | return HttpResponse.ok(.html("refresh")) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCUIElement.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @class NSString, XCElementSnapshot, XCUIApplication, XCUICoordinate, XCUIElementQuery; 10 | 11 | @interface XCUIElement () 12 | { 13 | BOOL _safeQueryResolutionEnabled; 14 | XCUIElementQuery *_query; 15 | XCElementSnapshot *_lastSnapshot; 16 | } 17 | 18 | @property BOOL safeQueryResolutionEnabled; // @synthesize safeQueryResolutionEnabled=_safeQueryResolutionEnabled; 19 | @property(retain) XCElementSnapshot *lastSnapshot; // @synthesize lastSnapshot=_lastSnapshot; 20 | @property(readonly) XCUIElementQuery *query; // @synthesize query=_query; 21 | #if !TARGET_OS_TV 22 | @property(readonly, nonatomic) UIInterfaceOrientation interfaceOrientation; 23 | #endif 24 | @property(readonly, copy) XCUICoordinate *hitPointCoordinate; 25 | @property(readonly) BOOL isTopLevelTouchBarElement; 26 | @property(readonly) BOOL isTouchBarElement; 27 | @property(readonly) BOOL hasKeyboardFocus; 28 | @property(readonly, nonatomic) XCUIApplication *application; 29 | // Added since Xcode 11.0 (beta) 30 | @property(readonly, copy) XCUIElement *excludingNonModalElements; 31 | // Added since Xcode 11.0 (GM) 32 | @property(readonly, copy) XCUIElement *includingNonModalElements; 33 | 34 | - (id)initWithElementQuery:(id)arg1; 35 | 36 | - (unsigned long long)traits; 37 | - (void)resolveHandleUIInterruption:(BOOL)arg1; 38 | // !!! deprecated since Xcode 11.0 39 | // Do not call directly 40 | - (void)resolve; 41 | - (BOOL)waitForExistenceWithTimeout:(double)arg1; 42 | - (BOOL)_waitForExistenceWithTimeout:(double)arg1; 43 | - (BOOL)evaluatePredicateForExpectation:(id)arg1 debugMessage:(id *)arg2; 44 | - (void)_swipe:(unsigned long long)arg1; 45 | - (void)_tapWithNumberOfTaps:(unsigned long long)arg1 numberOfTouches:(unsigned long long)arg2 activityTitle:(id)arg3; 46 | - (id)_highestNonWindowAncestorOfElement:(id)arg1 notSharedWithElement:(id)arg2; 47 | - (id)_pointsInFrame:(CGRect)arg1 numberOfTouches:(unsigned long long)arg2; 48 | - (CGPoint)_hitPointByAttemptingToScrollToVisibleSnapshot:(id)arg1; 49 | - (void)forcePress; 50 | 51 | // Available since Xcode 11.0 52 | - (_Bool)resolveOrRaiseTestFailure:(_Bool)arg1 error:(id *)arg2; 53 | - (void)resolveOrRaiseTestFailure; 54 | // Available since Xcode 10.0 55 | - (id)screenshot; 56 | 57 | @end -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Controllers/XCTestWDSourceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestAlertViewCommand.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import SwiftyJSON 12 | 13 | internal class XCTestWDSourceController: Controller { 14 | 15 | //MARK: Controller - Protocol 16 | static func routes() -> [(RequestRoute, RoutingCall)] { 17 | return [(RequestRoute("/wd/hub/session/:sessionId/source", "get"), source), 18 | (RequestRoute("/wd/hub/source", "get"), sourceWithoutSession), 19 | (RequestRoute("/wd/hub/session/:sessionId/accessibleSource", "get"), accessiblitySource), 20 | (RequestRoute("/wd/hub/accessibleSource", "get"), accessiblitySourceWithoutSession)] 21 | } 22 | 23 | static func shouldRegisterAutomatically() -> Bool { 24 | return false 25 | } 26 | 27 | //MARK: Routing Logic Specification 28 | internal static func source(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 29 | let _ = request.session?.application.query() 30 | request.session?.application.fb_nativeResolve() 31 | let temp = request.session?.application.tree() 32 | return XCTestWDResponse.response(session: request.session, value: JSON(JSON(temp!).rawString() ?? "")) 33 | } 34 | 35 | internal static func sourceWithoutSession(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 36 | let temp = XCTestWDSession.activeApplication()?.tree() 37 | return XCTestWDResponse.response(session: request.session, value: JSON(temp!)) 38 | } 39 | 40 | internal static func accessiblitySource(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 41 | let _ = request.session?.application.query() 42 | request.session?.application.fb_nativeResolve() 43 | let temp = request.session?.application.accessibilityTree() 44 | return XCTestWDResponse.response(session: request.session, value: JSON(JSON(temp!).rawString() ?? "")) 45 | } 46 | 47 | internal static func accessiblitySourceWithoutSession(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 48 | let temp = XCTestWDSessionManager.singleton.checkDefaultSession().application.accessibilityTree() 49 | return XCTestWDResponse.response(session: request.session, value: JSON(temp!)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/XCTestWDSessionTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDUnitTest.swift 3 | // XCTestWDUnitTest 4 | // 5 | // Created by SamuelZhaoY on 31/3/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Swifter 11 | import Nimble 12 | @testable import XCTestWD 13 | 14 | class XCTestWDSessionTest: XCTestWDUnitTestBase { 15 | 16 | func testApplicationLaunch() { 17 | XCTAssert(self.springApplication != nil, "application should not be nil") 18 | } 19 | 20 | func testFetchSystemApplicaiton() { 21 | let systemApplication = XCTestXCAXClientProxy.sharedClient().systemApplication() 22 | let springBoardApplication = XCTestWDApplication.create(byPID: pid_t(((systemApplication as! NSObject).value(forKey: "processIdentifier") as! NSNumber).intValue)) 23 | 24 | XCTAssert(springBoardApplication != nil, "application should not be nil") 25 | } 26 | 27 | func testSessionCreation() { 28 | XCTAssert(self.springApplication?.bundleID == XCTestWDSession.activeApplication()?.bundleID) 29 | 30 | let session = XCTestWDSession.sessionWithApplication(self.springApplication!) 31 | XCTestWDSessionManager.singleton.mountSession(session) 32 | 33 | XCTAssert(XCTestWDSessionManager.singleton.queryAll().keys.count == 1, "key length should be one, containing"); 34 | } 35 | 36 | func testSessionDeletion() { 37 | self.testSessionCreation() 38 | 39 | XCTestWDSessionManager.singleton.clearAll(); 40 | 41 | XCTAssert(XCTestWDSessionManager.singleton.queryAll().keys.count == 0, "key length shoud be zero") 42 | } 43 | 44 | func testSessionIDDeletion() { 45 | let session = XCTestWDSession.sessionWithApplication(self.springApplication!) 46 | XCTestWDSessionManager.singleton.mountSession(session) 47 | 48 | XCTAssert(XCTestWDSessionManager.singleton.queryAll().keys.count == 1, "key length should be one, containing"); 49 | 50 | XCTestWDSessionManager.singleton.deleteSession(session.identifier) 51 | XCTAssert(XCTestWDSessionManager.singleton.queryAll().keys.count == 0, "key length shoud be zero") 52 | } 53 | 54 | func testElementCache() { 55 | let uuid = XCTestWDSessionManager.commonCache.storeElement(self.springApplication!) 56 | expect(XCTestWDSessionManager.commonCache.elementForUUID(uuid)).to(equal(self.springApplication!)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestPrivateSymbols.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "XCTestPrivateSymbols.h" 11 | 12 | #import "XCRuntimeUtils.h" 13 | 14 | NSNumber *XCAXAIsVisibleAttribute; 15 | NSNumber *XCAXAIsElementAttribute; 16 | 17 | void (*XCSetDebugLogger)(id ); 18 | id (*XCDebugLogger)(void); 19 | 20 | __attribute__((constructor)) void LoadXCTestSymbols(void) 21 | { 22 | NSString *XC_kAXXCAttributeIsVisible = *(NSString*__autoreleasing*)RetrieveXCTestSymbol("XC_kAXXCAttributeIsVisible"); 23 | NSString *XC_kAXXCAttributeIsElement = *(NSString*__autoreleasing*)RetrieveXCTestSymbol("XC_kAXXCAttributeIsElement"); 24 | 25 | NSArray *(*XCAXAccessibilityAttributesForStringAttributes)(NSArray *list) = 26 | (NSArray *(*)(NSArray *))RetrieveXCTestSymbol("XCAXAccessibilityAttributesForStringAttributes"); 27 | 28 | XCSetDebugLogger = (void (*)(id ))RetrieveXCTestSymbol("XCSetDebugLogger"); 29 | XCDebugLogger = (id(*)(void))RetrieveXCTestSymbol("XCDebugLogger"); 30 | 31 | NSArray *accessibilityAttributes = XCAXAccessibilityAttributesForStringAttributes(@[XC_kAXXCAttributeIsVisible, XC_kAXXCAttributeIsElement]); 32 | XCAXAIsVisibleAttribute = accessibilityAttributes[0]; 33 | XCAXAIsElementAttribute = accessibilityAttributes[1]; 34 | 35 | NSCAssert(XCAXAIsVisibleAttribute != nil , @"Failed to retrieve FB_XCAXAIsVisibleAttribute", XCAXAIsVisibleAttribute); 36 | NSCAssert(XCAXAIsElementAttribute != nil , @"Failed to retrieve FB_XCAXAIsElementAttribute", XCAXAIsElementAttribute); 37 | } 38 | 39 | void *RetrieveXCTestSymbol(const char *name) 40 | { 41 | Class XCTestClass = NSClassFromString(@"XCTestCase"); 42 | NSCAssert(XCTestClass != nil, @"XCTest should be already linked", XCTestClass); 43 | NSString *XCTestBinary = [NSBundle bundleForClass:XCTestClass].executablePath; 44 | const char *binaryPath = XCTestBinary.UTF8String; 45 | NSCAssert(binaryPath != nil, @"XCTest binary path should not be nil", binaryPath); 46 | return FBRetrieveSymbolFromBinary(binaryPath, name); 47 | } 48 | 49 | int portNumber() 50 | { 51 | return XCTESTWD_PORT; 52 | } 53 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCAXClient_iOS.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "CDStructures.h" 8 | #import 9 | 10 | @class NSMutableDictionary; 11 | @class XCAccessibilityElement; 12 | 13 | @interface XCAXClient_iOS : NSObject 14 | { 15 | NSMutableDictionary *_userTestingNotificationHandlers; 16 | NSMutableDictionary *_cacheAccessibilityLoadedValuesForPIDs; 17 | unsigned long long *_alertNotificationCounter; 18 | } 19 | @property double AXTimeout; 20 | 21 | // Added since Xcode 10.2 22 | @property(readonly) id applicationProcessTracker; 23 | 24 | + (id)sharedClient; 25 | - (BOOL)_setAXTimeout:(double)arg1 error:(id *)arg2; 26 | - (NSData *)screenshotData; 27 | - (BOOL)performAction:(int)arg1 onElement:(id)arg2 value:(id)arg3 error:(id *)arg4; 28 | - (id)parameterizedAttributeForElement:(id)arg1 attribute:(id)arg2 parameter:(id)arg3; 29 | - (BOOL)setAttribute:(id)arg1 value:(id)arg2 element:(id)arg3 outError:(id *)arg4; 30 | - (id)attributesForElement:(id)arg1 attributes:(id)arg2; 31 | // since Xcode10 32 | - (id)attributesForElement:(id)arg1 attributes:(id)arg2 error:(id *)arg3; 33 | - (id)attributesForElementSnapshot:(id)arg1 attributeList:(id)arg2; 34 | - (id)snapshotForApplication:(id)arg1 attributeList:(id)arg2 parameters:(id)arg3; 35 | - (id)defaultParameters; 36 | - (id)defaultAttributes; 37 | - (void)notifyWhenViewControllerViewDidDisappearReply:(CDUnknownBlockType)arg1; 38 | - (void)notifyWhenViewControllerViewDidAppearReply:(CDUnknownBlockType)arg1; 39 | - (void)notifyWhenNoAnimationsAreActiveForApplication:(id)arg1 reply:(CDUnknownBlockType)arg2; 40 | - (void)notifyWhenEventLoopIsIdleForApplication:(id)arg1 reply:(CDUnknownBlockType)arg2; 41 | - (id)interruptingUIElementAffectingSnapshot:(id)arg1; 42 | - (void)handleAccessibilityNotification:(int)arg1 withPayload:(id)arg2; 43 | - (void)notifyOnNextOccurrenceOfUserTestingEvent:(id)arg1 handler:(CDUnknownBlockType)arg2; 44 | - (void)handleUserTestingNotification:(id)arg1; 45 | - (id)elementAtPoint:(CGPoint)arg1 error:(id *)arg2; 46 | - (BOOL)cachedAccessibilityLoadedValueForPID:(int)arg1; 47 | - (id)activeApplications; 48 | - (id)systemApplication; 49 | - (BOOL)enableFauxCollectionViewCells:(id *)arg1; 50 | - (BOOL)loadAccessibility:(id *)arg1; 51 | - (BOOL)_registerForAXNotification:(int)arg1 error:(id *)arg2; 52 | - (BOOL)_loadAccessibility:(id *)arg1; 53 | - (id)init; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCUIApplication.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @class NSArray, NSDictionary, NSString, XCAccessibilityElement, XCApplicationQuery, XCUIApplicationImpl; 10 | 11 | @interface XCUIApplication () 12 | { 13 | BOOL _ancillary; 14 | BOOL _doesNotHandleUIInterruptions; 15 | BOOL _idleAnimationWaitEnabled; 16 | XCUIElement *_keyboard; 17 | NSArray *_launchArguments; 18 | NSDictionary *_launchEnvironment; 19 | XCUIApplicationImpl *_applicationImpl; 20 | XCApplicationQuery *_applicationQuery; 21 | unsigned long long _generation; 22 | } 23 | @property unsigned long long generation; // @synthesize generation=_generation; 24 | @property(retain) XCApplicationQuery *applicationQuery; // @synthesize applicationQuery=_applicationQuery; 25 | @property(retain) XCUIApplicationImpl *applicationImpl; // @synthesize applicationQuery=_applicationQuery; 26 | @property(readonly, copy) NSString *bundleID; // @synthesize bundleID=_bundleID; 27 | @property(readonly, copy) NSString *path; // @synthesize path=_path; 28 | @property BOOL ancillary; // @synthesize ancillary=_ancillary; 29 | @property(readonly) XCUIElement *keyboard; // @synthesize keyboard=_keyboard; 30 | 31 | @property(getter=isIdleAnimationWaitEnabled) BOOL idleAnimationWaitEnabled; // @synthesize idleAnimationWaitEnabled=_idleAnimationWaitEnabled; 32 | @property(nonatomic) BOOL doesNotHandleUIInterruptions; // @synthesize doesNotHandleUIInterruptions=_doesNotHandleUIInterruptions; 33 | @property(readonly) BOOL fauxCollectionViewCellsEnabled; 34 | #if !TARGET_OS_TV 35 | @property(readonly, nonatomic) UIInterfaceOrientation interfaceOrientation; //TODO tvos 36 | #endif 37 | @property(readonly, nonatomic) BOOL running; 38 | @property(nonatomic) pid_t processID; // @synthesize processID=_processID; 39 | @property(readonly) XCAccessibilityElement *accessibilityElement; 40 | 41 | + (instancetype)appWithPID:(pid_t)processID; 42 | + (instancetype)applicationWithPID:(pid_t)processID; 43 | - (void)activate; 44 | 45 | - (void)dismissKeyboard; 46 | - (BOOL)setFauxCollectionViewCellsEnabled:(BOOL)arg1 error:(id *)arg2; 47 | - (void)_waitForViewControllerViewDidDisappearWithTimeout:(double)arg1; 48 | - (void)_waitForQuiescence; 49 | - (void)terminate; 50 | - (void)_launchUsingXcode:(BOOL)arg1; 51 | - (void)launch; 52 | - (id)application; 53 | - (id)description; 54 | - (id)lastSnapshot; 55 | - (XCUIElementQuery *)query; 56 | - (void)clearQuery; 57 | - (void)resolveHandleUIInterruption:(BOOL)arg1; 58 | - (id)initPrivateWithPath:(id)arg1 bundleID:(id)arg2; 59 | - (id)init; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/Macaca/NSPredicate+XCTestWD.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSPredicate+XCTestWD.m 3 | // XCTestWD 4 | // 5 | // Created by SamuelZhaoY on 18/8/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | #import "NSPredicate+XCTestWD.h" 10 | 11 | @implementation NSPredicate(XCTestWD) 12 | 13 | + (instancetype)xctestWDPredicateWithFormat:(NSString *)predicateFormat 14 | { 15 | NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateFormat arguments:nil]; 16 | NSPredicate *hackPredicate = [NSPredicate predicateWithFormat:self.forceResolvePredicateString]; 17 | return [NSCompoundPredicate andPredicateWithSubpredicates:@[predicate, hackPredicate]]; 18 | } 19 | 20 | + (NSString *)forceResolvePredicateString 21 | { 22 | return @"1 == 1 or identifier == 0 or frame == 0 or value == 0 or title == 0 or label == 0 or elementType == 0 or enabled == 0 or placeholderValue == 0"; 23 | } 24 | 25 | + (instancetype)xctestWDPredicateWithPredicate:(NSPredicate *)original comparisonModifier:(NSPredicate *(^)(NSComparisonPredicate *))comparisonModifier 26 | { 27 | if ([original isKindOfClass:NSCompoundPredicate.class]) { 28 | NSCompoundPredicate *compPred = (NSCompoundPredicate *)original; 29 | NSMutableArray *predicates = [NSMutableArray array]; 30 | for (NSPredicate *predicate in [compPred subpredicates]) { 31 | if ([predicate.predicateFormat.lowercaseString isEqualToString:NSPredicate.forceResolvePredicateString.lowercaseString]) { 32 | // Do not translete this predicate 33 | [predicates addObject:predicate]; 34 | continue; 35 | } 36 | NSPredicate *newPredicate = [self.class xctestWDPredicateWithPredicate:predicate comparisonModifier:comparisonModifier]; 37 | if (nil != newPredicate) { 38 | [predicates addObject:newPredicate]; 39 | } 40 | } 41 | return [[NSCompoundPredicate alloc] initWithType:compPred.compoundPredicateType 42 | subpredicates:predicates]; 43 | } 44 | if ([original isKindOfClass:NSComparisonPredicate.class]) { 45 | return comparisonModifier((NSComparisonPredicate *)original); 46 | } 47 | return original; 48 | } 49 | 50 | + (instancetype)xctestWDformatSearchPredicate:(NSPredicate *)input 51 | { 52 | return [self.class xctestWDPredicateWithPredicate:input comparisonModifier:^NSPredicate *(NSComparisonPredicate *cp) { 53 | return [NSComparisonPredicate predicateWithLeftExpression:[cp leftExpression] 54 | rightExpression:[cp rightExpression] 55 | modifier:cp.comparisonPredicateModifier 56 | type:cp.predicateOperatorType 57 | options:cp.options]; 58 | }]; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD.xcodeproj/xcshareddata/xcschemes/XCTestWD.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/XCTestWDReponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDReponse.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 24/4/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | import Swifter 12 | 13 | internal class XCTestWDResponse { 14 | 15 | //MARK: Model & Constructor 16 | private var sessionId:String! 17 | private var status:WDStatus! 18 | private var value:JSON? 19 | 20 | private init(_ sessionId:String, _ status:WDStatus, _ value:JSON?) { 21 | self.sessionId = sessionId 22 | self.status = status 23 | self.value = value ?? JSON("") 24 | } 25 | 26 | private func response() -> HttpResponse { 27 | let response : JSON = ["sessionId":self.sessionId, 28 | "status":self.status.rawValue, 29 | "value":self.value as Any] 30 | let rawString = response.rawString(options:[])?.replacingOccurrences(of: "\n", with: "") 31 | return rawString != nil ? HttpResponse.ok(.text(rawString!)) : HttpResponse.ok(.text("{}")) 32 | } 33 | 34 | //MARK: Utils 35 | static func response(session:XCTestWDSession?, value:JSON?) -> HttpResponse { 36 | return XCTestWDResponse(session?.identifier ?? "", WDStatus.Success, value ?? JSON("{}")).response() 37 | } 38 | 39 | static func response(session:XCTestWDSession? ,error:WDStatus) -> HttpResponse { 40 | return XCTestWDResponse(session?.identifier ?? "", error, nil).response() 41 | } 42 | 43 | //MARK: Element Response 44 | static func responseWithCacheElement(_ element:XCUIElement, _ elementCache:XCTestWDElementCache) -> HttpResponse { 45 | let elementUUID = elementCache.storeElement(element) 46 | return getResponseFromDictionary(dictionaryWithElement(element, elementUUID, false)) 47 | } 48 | 49 | static func responsWithCacheElements(_ elements:[XCUIElement], _ elementCache:XCTestWDElementCache) -> HttpResponse { 50 | var response = [[String:String]]() 51 | for element in elements { 52 | let elementUUID = elementCache.storeElement(element) 53 | response.append(dictionaryWithElement(element, elementUUID, false)) 54 | } 55 | return XCTestWDResponse.response(session: nil, value: JSON(response)) 56 | } 57 | 58 | // ------------ Internal Method --------- 59 | private static func dictionaryWithElement(_ element:XCUIElement, _ elementUUID:String, _ compact:Bool) -> [String:String] { 60 | var dictionary = [String:String](); 61 | dictionary["ELEMENT"] = elementUUID 62 | 63 | if compact == false { 64 | dictionary["label"] = element.wdLabel() 65 | dictionary["type"] = element.wdType() 66 | } 67 | 68 | return dictionary 69 | } 70 | 71 | private static func getResponseFromDictionary(_ dictionary:[String:String]) -> HttpResponse { 72 | return XCTestWDResponse.response(session:nil, value:JSON(dictionary)) 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /lib/proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const request = require('request'); 4 | 5 | const _ = require('./helper'); 6 | const logger = require('./logger'); 7 | 8 | class XCProxy { 9 | constructor(options) { 10 | Object.assign(this, { 11 | scheme: 'http', 12 | proxyHost: '127.0.0.1', 13 | proxyPort: 8001, 14 | urlBase: 'wd/hub', 15 | sessionId: null, 16 | originSessionId: null 17 | }, options); 18 | } 19 | 20 | handleNewUrl(url) { 21 | const sessionReg = /\/session\/([^\/]+)/; 22 | const wdSessionReg = new RegExp(`${this.urlBase}\/session\/([^\/]+)`); 23 | url = `${this.scheme}://${this.proxyHost}:${this.proxyPort}${url}`; 24 | 25 | if (sessionReg.test(url) && this.sessionId) { 26 | this.originSessionId = url.match(sessionReg)[1]; 27 | url = url.replace(wdSessionReg, `${this.urlBase}/session/${this.sessionId}`); 28 | } 29 | return url; 30 | } 31 | 32 | send(url, method, body) { 33 | return new Promise((resolve, reject) => { 34 | method = method.toUpperCase(); 35 | const newUrl = this.handleNewUrl(url); 36 | const retryCount = 10; 37 | const retryInterval = 2000; 38 | 39 | const reqOpts = { 40 | url: newUrl, 41 | method: method, 42 | headers: { 43 | 'Content-type': 'application/json;charset=UTF=8' 44 | }, 45 | resolveWithFullResponse: true 46 | }; 47 | 48 | if (body && (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT')) { 49 | if (typeof body !== 'object') { 50 | body = JSON.parse(body); 51 | } 52 | reqOpts.json = body; 53 | } 54 | 55 | logger.debug(`Proxy: ${url}:${method} to ${newUrl}:${method} with body: ${_.truncate(JSON.stringify(body), { 56 | length: 200 57 | })}`); 58 | 59 | _.retry(() => { 60 | return new Promise((_resolve, _reject) => { 61 | request(reqOpts, (error, res, body) => { 62 | if (error) { 63 | logger.debug(`xctest client proxy error with: ${error}`); 64 | return _reject(error); 65 | } 66 | 67 | if (!body) { 68 | logger.debug('xctest client proxy received no data.'); 69 | return _reject('No data received from XCTestWD.'); 70 | } 71 | 72 | if (typeof body !== 'object') { 73 | try { 74 | body = JSON.parse(body); 75 | } catch (e) { 76 | logger.debug(`Fail to parse body: ${e}`); 77 | } 78 | } 79 | 80 | if (body && body.sessionId) { 81 | this.sessionId = body.sessionId; 82 | body.sessionId = this.originSessionId; 83 | } 84 | 85 | logger.debug(`Got response with status ${res.statusCode}: ${_.truncate(JSON.stringify(body), { 86 | length: 200 87 | })}`); 88 | _resolve(body); 89 | }); 90 | 91 | }); 92 | }, retryInterval, retryCount).then(resolve, reject); 93 | }); 94 | } 95 | } 96 | 97 | module.exports = XCProxy; 98 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/XCTestWDAlert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDAlert.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 27/4/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal class XCTestWDAlert { 12 | 13 | private let application:XCUIApplication! 14 | 15 | init(_ application:XCUIApplication) { 16 | self.application = application 17 | } 18 | 19 | //MARK: Commands 20 | 21 | //TODO: works on XPATH and then works on getting text 22 | internal func text() -> String? { 23 | 24 | let alertElement = self.alertElement() 25 | if alertElement != nil { 26 | return nil 27 | } 28 | 29 | return nil 30 | } 31 | //TODO: works on XPATH and then works on getting this 32 | internal func keys(input: String) -> Bool { 33 | 34 | return false 35 | } 36 | 37 | internal func accept() -> Bool { 38 | 39 | let alertElement = self.alertElement() 40 | let buttons = self.alertElement()?.descendants(matching: XCUIElement.ElementType.button).allElementsBoundByIndex 41 | var defaultButton:XCUIElement? 42 | 43 | if alertElement?.elementType == XCUIElement.ElementType.alert { 44 | defaultButton = (buttons?.last) 45 | } else { 46 | defaultButton = (buttons?.first) 47 | } 48 | 49 | if defaultButton != nil { 50 | defaultButton?.tap() 51 | return true 52 | } 53 | 54 | return false 55 | } 56 | 57 | internal func dismiss() -> Bool { 58 | 59 | let alertElement = self.alertElement() 60 | let buttons = self.alertElement()?.descendants(matching: XCUIElement.ElementType.button).allElementsBoundByIndex 61 | var defaultButton:XCUIElement? 62 | 63 | if alertElement?.elementType == XCUIElement.ElementType.alert { 64 | defaultButton = (buttons?.first) 65 | } else { 66 | defaultButton = (buttons?.last) 67 | } 68 | 69 | if defaultButton != nil { 70 | defaultButton?.tap() 71 | return true 72 | } 73 | 74 | return false 75 | } 76 | 77 | //MARK: Utils 78 | private func alertElement() -> XCUIElement? { 79 | var alert = self.application.alerts.element 80 | // Check default alerts exists 81 | if !(alert.exists) { 82 | alert = self.application.sheets.element 83 | // Check actionsheet exists 84 | if !(alert.exists) { 85 | let sprintboard = XCUIApplication.init(privateWithPath: nil, bundleID: "com.apple.springboard") 86 | alert = (sprintboard?.alerts.element) ?? alert 87 | if !(alert.exists) { 88 | return nil 89 | } 90 | } 91 | } 92 | 93 | alert.fb_nativeResolve() 94 | self.application.query() 95 | self.application.fb_nativeResolve() 96 | return alert 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD.xcodeproj/xcshareddata/xcschemes/XCTestWDAggregation.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestManager_IDEInterface-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSArray, NSDictionary, NSNumber, NSString, XCAccessibilityElement, XCActivityRecord, XCElementSnapshot; 8 | 9 | @protocol XCTestManager_IDEInterface 10 | - (id)_XCT_handleCrashReportData:(NSData *)arg1 fromFileWithName:(NSString *)arg2; 11 | - (id)_XCT_nativeFocusItemDidChangeAtTime:(NSNumber *)arg1 parameterSnapshot:(XCElementSnapshot *)arg2 applicationSnapshot:(XCElementSnapshot *)arg3; 12 | - (id)_XCT_recordedEventNames:(NSArray *)arg1 timestamp:(NSNumber *)arg2 duration:(NSNumber *)arg3 startLocation:(NSDictionary *)arg4 startElementSnapshot:(XCElementSnapshot *)arg5 startApplicationSnapshot:(XCElementSnapshot *)arg6 endLocation:(NSDictionary *)arg7 endElementSnapshot:(XCElementSnapshot *)arg8 endApplicationSnapshot:(XCElementSnapshot *)arg9; 13 | - (id)_XCT_testCase:(NSString *)arg1 method:(NSString *)arg2 didFinishActivity:(XCActivityRecord *)arg3; 14 | - (id)_XCT_testCase:(NSString *)arg1 method:(NSString *)arg2 willStartActivity:(XCActivityRecord *)arg3; 15 | - (id)_XCT_recordedOrientationChange:(NSString *)arg1; 16 | - (id)_XCT_recordedFirstResponderChangedWithApplicationSnapshot:(XCElementSnapshot *)arg1; 17 | - (id)_XCT_exchangeCurrentProtocolVersion:(NSNumber *)arg1 minimumVersion:(NSNumber *)arg2; 18 | - (id)_XCT_recordedKeyEventsWithApplicationSnapshot:(XCElementSnapshot *)arg1 characters:(NSString *)arg2 charactersIgnoringModifiers:(NSString *)arg3 modifierFlags:(NSNumber *)arg4; 19 | - (id)_XCT_logDebugMessage:(NSString *)arg1; 20 | - (id)_XCT_logMessage:(NSString *)arg1; 21 | - (id)_XCT_testMethod:(NSString *)arg1 ofClass:(NSString *)arg2 didMeasureMetric:(NSDictionary *)arg3 file:(NSString *)arg4 line:(NSNumber *)arg5; 22 | - (id)_XCT_testCase:(NSString *)arg1 method:(NSString *)arg2 didStallOnMainThreadInFile:(NSString *)arg3 line:(NSNumber *)arg4; 23 | - (id)_XCT_testCaseDidFinishForTestClass:(NSString *)arg1 method:(NSString *)arg2 withStatus:(NSString *)arg3 duration:(NSNumber *)arg4; 24 | - (id)_XCT_testCaseDidFailForTestClass:(NSString *)arg1 method:(NSString *)arg2 withMessage:(NSString *)arg3 file:(NSString *)arg4 line:(NSNumber *)arg5; 25 | - (id)_XCT_testCaseDidStartForTestClass:(NSString *)arg1 method:(NSString *)arg2; 26 | - (id)_XCT_testSuite:(NSString *)arg1 didFinishAt:(NSString *)arg2 runCount:(NSNumber *)arg3 withFailures:(NSNumber *)arg4 unexpected:(NSNumber *)arg5 testDuration:(NSNumber *)arg6 totalDuration:(NSNumber *)arg7; 27 | - (id)_XCT_testSuite:(NSString *)arg1 didStartAt:(NSString *)arg2; 28 | - (id)_XCT_initializationForUITestingDidFailWithError:(NSError *)arg1; 29 | - (id)_XCT_didBeginInitializingForUITesting; 30 | - (id)_XCT_didFinishExecutingTestPlan; 31 | - (id)_XCT_didBeginExecutingTestPlan; 32 | - (id)_XCT_testBundleReadyWithProtocolVersion:(NSNumber *)arg1 minimumVersion:(NSNumber *)arg2; 33 | - (id)_XCT_getProgressForLaunch:(id)arg1; 34 | - (id)_XCT_terminateProcess:(id)arg1; 35 | - (id)_XCT_launchProcessWithPath:(NSString *)arg1 bundleID:(NSString *)arg2 arguments:(NSArray *)arg3 environmentVariables:(NSDictionary *)arg4; 36 | @end 37 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWDUnitTest/ControllerTests/XCTestWDSourceControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDSourceControllerTests.swift 3 | // XCTestWDUnitTest 4 | // 5 | // Created by SamuelZhaoY on 6/5/18. 6 | // Copyright © 2018 XCTestWD. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Nimble 11 | import SwiftyJSON 12 | @testable import XCTestWD 13 | import Swifter 14 | 15 | class XCTestWDSourceControllerTests: XCTestWDUnitTestBase { 16 | 17 | func testSourceRetrieve() { 18 | let request = Swifter.HttpRequest.init() 19 | let session = XCTestWDSessionManager.singleton.checkDefaultSession() 20 | XCTestWDSessionManager.singleton.mountSession(session) 21 | request.params["sessionId"] = session.identifier 22 | 23 | // case 1. test source in home panel, full amount of elements available. 24 | XCUIDevice.shared.press(XCUIDevice.Button.home) 25 | var response = XCTestWDSourceController.source(request: request) 26 | var contentJSON = XCTestWDUnitTestBase.getResponseData(response) 27 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 28 | 29 | // case 2. test source in web app. 30 | request.body = [UInt8]((try? JSON(["value":"//*[@name=\"Safari\"]","using":"xpath"]).rawData()) ?? Data()) 31 | response = XCTestWDElementController.findElement(request: request) 32 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 33 | var jsonResponse = XCTestWDUnitTestBase.getResponseData(response) 34 | expect(jsonResponse["value"]["ELEMENT"]).toNot(beNil()) 35 | 36 | // click and enter safari 37 | request.path = "/element/\(jsonResponse["value"]["ELEMENT"])" 38 | response = XCTestWDElementController.click(request: request) 39 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 40 | 41 | response = XCTestWDSourceController.sourceWithoutSession(request: request) 42 | contentJSON = XCTestWDUnitTestBase.getResponseData(response) 43 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 44 | 45 | // case 3. test source in native app. 46 | XCUIDevice.shared.press(XCUIDevice.Button.home) 47 | // sliding to first panel 48 | XCUIDevice.shared.press(XCUIDevice.Button.home) 49 | 50 | request.body = [UInt8]((try? JSON(["value":"//*[@name=\"Messages\"]","using":"xpath"]).rawData()) ?? Data()) 51 | response = XCTestWDElementController.findElement(request: request) 52 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 53 | jsonResponse = XCTestWDUnitTestBase.getResponseData(response) 54 | expect(jsonResponse["value"]["ELEMENT"]).toNot(beNil()) 55 | 56 | // click and enter safari 57 | request.path = "/element/\(jsonResponse["value"]["ELEMENT"])" 58 | response = XCTestWDElementController.click(request: request) 59 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 60 | 61 | response = XCTestWDSourceController.sourceWithoutSession(request: request) 62 | contentJSON = XCTestWDUnitTestBase.getResponseData(response) 63 | expect(contentJSON["status"].int).to(equal(WDStatus.Success.rawValue)) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCUIElementQuery.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "CDStructures.h" 8 | #import 9 | #import "XCTElementSetTransformer-Protocol.h" 10 | 11 | @class NSArray, NSOrderedSet, NSString, XCUIApplication, XCUIElement; 12 | 13 | @interface XCUIElementQuery () 14 | { 15 | BOOL _changesScope; 16 | NSString *_queryDescription; 17 | XCUIElementQuery *_inputQuery; 18 | CDUnknownBlockType _filter; 19 | unsigned long long _expressedType; 20 | NSArray *_expressedIdentifiers; 21 | NSOrderedSet *_lastInput; 22 | NSOrderedSet *_lastOutput; 23 | XCElementSnapshot *_rootElementSnapshot; 24 | // Added since Xcode 11.0 (beta) 25 | BOOL _modalViewPruningDisabled; 26 | } 27 | 28 | @property(copy) NSOrderedSet *lastOutput; // @synthesize lastOutput=_lastOutput; 29 | @property(copy) NSOrderedSet *lastInput; // @synthesize lastInput=_lastInput; 30 | @property(copy) NSArray *expressedIdentifiers; // @synthesize expressedIdentifiers=_expressedIdentifiers; 31 | @property unsigned long long expressedType; // @synthesize expressedType=_expressedType; 32 | @property BOOL changesScope; // @synthesize changesScope=_changesScope; 33 | @property(readonly, copy) CDUnknownBlockType filter; // @synthesize filter=_filter; 34 | // Added since Xcode 11.0 (beta) 35 | @property BOOL modalViewPruningDisabled; // @synthesize modalViewPruningDisabled=_modalViewPruningDisabled; 36 | @property(readonly) XCUIElementQuery *inputQuery; // @synthesize inputQuery=_inputQuery; 37 | @property(readonly, copy) NSString *queryDescription; // @synthesize queryDescription=_queryDescription; 38 | @property(readonly, copy) NSString *elementDescription; 39 | @property(readonly) XCUIApplication *application; 40 | @property(retain) XCElementSnapshot *rootElementSnapshot; // @synthesize rootElementSnapshot=_rootElementSnapshot; 41 | @property(retain) NSObject *transformer; // @synthesize transformer = _transformer; 42 | 43 | // Added since Xcode 11.0 (beta) 44 | @property(readonly, copy) XCUIElementQuery *excludingNonModalElements; 45 | // Added since Xcode 11.0 (GM) 46 | @property(readonly, copy) XCUIElementQuery *includingNonModalElements; 47 | 48 | - (id)matchingSnapshotsWithError:(id *)arg1; 49 | - (id)matchingSnapshotsHandleUIInterruption:(BOOL)arg1 withError:(id *)arg2; 50 | - (id)_elementMatchingAccessibilityElementOfSnapshot:(id)arg1; 51 | - (id)_containingPredicate:(id)arg1 queryDescription:(id)arg2; 52 | - (id)_predicateWithType:(unsigned long long)arg1 identifier:(id)arg2; 53 | - (id)_queryWithPredicate:(id)arg1; 54 | - (id)sorted:(CDUnknownBlockType)arg1; 55 | - (id)descending:(unsigned long long)arg1; 56 | - (id)ascending:(unsigned long long)arg1; 57 | - (id)filter:(CDUnknownBlockType)arg1; 58 | - (id)_debugInfoWithIndent:(id *)arg1; 59 | - (id)_derivedExpressedIdentifiers; 60 | - (unsigned long long)_derivedExpressedType; 61 | - (id)initWithInputQuery:(id)arg1 queryDescription:(id)arg2 filter:(CDUnknownBlockType)arg3; 62 | 63 | // Deprecated since Xcode 11.0 64 | - (XCElementSnapshot *)elementSnapshotForDebugDescription; 65 | // Added since Xcode 11.0 66 | - (XCElementSnapshot *)elementSnapshotForDebugDescriptionWithNoMatchesMessage:(id *)arg1; 67 | 68 | /*! DO NOT USE DIRECTLY! Please use fb_firstMatch instead */ 69 | - (XCUIElement *)firstMatch; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD.xcodeproj/xcshareddata/xcschemes/XCTestWDUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestManager_ManagerInterface-Protocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | @class NSArray, NSDictionary, NSNumber, NSString, NSUUID, XCAccessibilityElement, XCDeviceEvent, XCSynthesizedEventRecord, XCTouchGesture, NSXPCListenerEndpoint; 10 | 11 | @protocol XCTestManager_ManagerInterface 12 | - (void)_XCT_loadAccessibilityWithTimeout:(double)arg1 reply:(void (^)(BOOL, NSError *))arg2; 13 | - (void)_XCT_injectVoiceRecognitionAudioInputPaths:(NSArray *)arg1 completion:(void (^)(BOOL, NSError *))arg2; 14 | - (void)_XCT_injectAssistantRecognitionStrings:(NSArray *)arg1 completion:(void (^)(BOOL, NSError *))arg2; 15 | - (void)_XCT_startSiriUIRequestWithAudioFileURL:(NSURL *)arg1 completion:(void (^)(BOOL, NSError *))arg2; 16 | - (void)_XCT_startSiriUIRequestWithText:(NSString *)arg1 completion:(void (^)(BOOL, NSError *))arg2; 17 | - (void)_XCT_requestDTServiceHubConnectionWithReply:(void (^)(NSXPCListenerEndpoint *, NSError *))arg1; 18 | - (void)_XCT_enableFauxCollectionViewCells:(void (^)(BOOL, NSError *))arg1; 19 | - (void)_XCT_setAXTimeout:(double)arg1 reply:(void (^)(int))arg2; 20 | - (void)_XCT_requestScreenshotWithReply:(void (^)(NSData *, NSError *))arg1; 21 | - (void)_XCT_sendString:(NSString *)arg1 maximumFrequency:(NSUInteger)arg2 completion:(void (^)(NSError *))arg3; 22 | - (void)_XCT_updateDeviceOrientation:(long long)arg1 completion:(void (^)(NSError *))arg2; 23 | - (void)_XCT_performDeviceEvent:(XCDeviceEvent *)arg1 completion:(void (^)(NSError *))arg2; 24 | - (void)_XCT_synthesizeEvent:(XCSynthesizedEventRecord *)arg1 completion:(void (^)(NSError *))arg2; 25 | - (void)_XCT_requestElementAtPoint:(CGPoint)arg1 reply:(void (^)(XCAccessibilityElement *, NSError *))arg2; 26 | - (void)_XCT_fetchParameterizedAttributeForElement:(XCAccessibilityElement *)arg1 attributes:(NSNumber *)arg2 parameter:(id)arg3 reply:(void (^)(id, NSError *))arg4; 27 | - (void)_XCT_setAttribute:(NSNumber *)arg1 value:(id)arg2 element:(XCAccessibilityElement *)arg3 reply:(void (^)(BOOL, NSError *))arg4; 28 | - (void)_XCT_fetchAttributesForElement:(XCAccessibilityElement *)arg1 attributes:(NSArray *)arg2 reply:(void (^)(NSDictionary *, NSError *))arg3; 29 | - (void)_XCT_snapshotForElement:(XCAccessibilityElement *)arg1 attributes:(NSArray *)arg2 parameters:(NSDictionary *)arg3 reply:(void (^)(XCElementSnapshot *, NSError *))arg4; 30 | - (void)_XCT_terminateApplicationWithBundleID:(NSString *)arg1 completion:(void (^)(NSError *))arg2; 31 | - (void)_XCT_performAccessibilityAction:(int)arg1 onElement:(XCAccessibilityElement *)arg2 withValue:(id)arg3 reply:(void (^)(NSError *))arg4; 32 | - (void)_XCT_unregisterForAccessibilityNotification:(int)arg1 withRegistrationToken:(NSNumber *)arg2 reply:(void (^)(NSError *))arg3; 33 | - (void)_XCT_registerForAccessibilityNotification:(int)arg1 reply:(void (^)(NSNumber *, NSError *))arg2; 34 | - (void)_XCT_launchApplicationWithBundleID:(NSString *)arg1 arguments:(NSArray *)arg2 environment:(NSDictionary *)arg3 completion:(void (^)(NSError *))arg4; 35 | - (void)_XCT_startMonitoringApplicationWithBundleID:(NSString *)arg1; 36 | - (void)_XCT_requestBackgroundAssertionForPID:(int)arg1 reply:(void (^)(BOOL))arg2; 37 | - (void)_XCT_requestBackgroundAssertionWithReply:(void (^)(void))arg1; 38 | - (void)_XCT_registerTarget; 39 | - (void)_XCT_requestEndpointForTestTargetWithPID:(int)arg1 preferredBackendPath:(NSString *)arg2 reply:(void (^)(NSXPCListenerEndpoint *, NSError *))arg3; 40 | - (void)_XCT_requestSocketForSessionIdentifier:(NSUUID *)arg1 reply:(void (^)(NSFileHandle *))arg2; 41 | - (void)_XCT_exchangeProtocolVersion:(unsigned long long)arg1 reply:(void (^)(unsigned long long))arg2; 42 | @end 43 | -------------------------------------------------------------------------------- /lib/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const macacaUtils = require('macaca-utils'); 4 | const childProcess = require('child_process'); 5 | const logger = require('./logger'); 6 | 7 | var _ = macacaUtils.merge({}, macacaUtils); 8 | 9 | _.sleep = function(ms) { 10 | return new Promise(resolve => { 11 | setTimeout(resolve, ms); 12 | }); 13 | }; 14 | 15 | _.retry = function(func, interval, num) { 16 | return new Promise((resolve, reject) => { 17 | func().then(resolve, err => { 18 | if (num > 0 || typeof num === 'undefined') { 19 | _.sleep(interval).then(() => { 20 | logger.debug(`retrying... (${num} retries left)`); 21 | resolve(_.retry(func, interval, num - 1)); 22 | }); 23 | } else { 24 | reject(err); 25 | } 26 | }); 27 | }); 28 | }; 29 | 30 | _.waitForCondition = function(func, wait/* ms*/, interval/* ms*/) { 31 | wait = wait || 5000; 32 | interval = interval || 500; 33 | let start = Date.now(); 34 | let end = start + wait; 35 | 36 | const fn = function() { 37 | return new Promise((resolve, reject) => { 38 | const continuation = (res, rej) => { 39 | let now = Date.now(); 40 | 41 | if (now < end) { 42 | res(_.sleep(interval).then(fn)); 43 | } else { 44 | rej(`Wait For Condition timeout ${wait}`); 45 | } 46 | }; 47 | func().then(isOk => { 48 | 49 | if (isOk) { 50 | resolve(); 51 | } else { 52 | continuation(resolve, reject); 53 | } 54 | }).catch(() => { 55 | continuation(resolve, reject); 56 | }); 57 | }); 58 | }; 59 | return fn(); 60 | }; 61 | 62 | _.escapeString = function(str) { 63 | return str 64 | .replace(/[\\]/g, '\\\\') 65 | .replace(/[\/]/g, '\\/') 66 | .replace(/[\b]/g, '\\b') 67 | .replace(/[\f]/g, '\\f') 68 | .replace(/[\n]/g, '\\n') 69 | .replace(/[\r]/g, '\\r') 70 | .replace(/[\t]/g, '\\t') 71 | .replace(/[\"]/g, '\\"') 72 | .replace(/\\'/g, "\\'"); 73 | }; 74 | 75 | _.exec = function(cmd, opts) { 76 | return new Promise((resolve, reject) => { 77 | childProcess.exec(cmd, _.merge({ 78 | maxBuffer: 1024 * 512, 79 | wrapArgs: false 80 | }, opts || {}), (err, stdout) => { 81 | if (err) { 82 | return reject(err); 83 | } 84 | resolve(_.trim(stdout)); 85 | }); 86 | }); 87 | }; 88 | 89 | _.spawn = function() { 90 | var args = Array.prototype.slice.call(arguments); 91 | 92 | return new Promise((resolve, reject) => { 93 | var stdout = ''; 94 | var stderr = ''; 95 | var child = childProcess.spawn.apply(childProcess, args); 96 | 97 | child.on('error', error => { 98 | reject(error); 99 | }); 100 | 101 | child.stdout.on('data', data => { 102 | stdout += data; 103 | }); 104 | 105 | child.stderr.on('data', data => { 106 | stderr += data; 107 | }); 108 | 109 | child.on('close', code => { 110 | var error; 111 | if (code) { 112 | error = new Error(stderr); 113 | error.code = code; 114 | return reject(error); 115 | } 116 | resolve([stdout, stderr]); 117 | }); 118 | }); 119 | }; 120 | 121 | var Defer = function() { 122 | this._resolve = null; 123 | this._reject = null; 124 | this.promise = new Promise((resolve, reject) => { 125 | this._resolve = resolve; 126 | this._reject = reject; 127 | }); 128 | }; 129 | 130 | Defer.prototype.resolve = function(data) { 131 | this._resolve(data); 132 | }; 133 | 134 | Defer.prototype.reject = function(err) { 135 | this._reject(err); 136 | }; 137 | 138 | _.Defer = Defer; 139 | 140 | module.exports = _; 141 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Controllers/XCTestWDAlertController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestAlertViewCommand.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import SwiftyJSON 12 | import CocoaLumberjackSwift 13 | 14 | internal class XCTestWDAlertController: Controller { 15 | 16 | //MARK: Controller - Protocol 17 | static func routes() -> [(RequestRoute, RoutingCall)] { 18 | return [(RequestRoute("/wd/hub/session/:sessionId/accept_alert", "post"), acceptAlert), 19 | (RequestRoute("/wd/hub/session/:sessionId/dismiss_alert", "post"), dismissAlert), 20 | (RequestRoute("/wd/hub/session/:sessionId/alert_text", "get"), alertText), 21 | (RequestRoute("/wd/hub/session/:sessionId/alert_text", "post"), alertKeys)] 22 | } 23 | 24 | static func shouldRegisterAutomatically() -> Bool { 25 | return false 26 | } 27 | 28 | //MARK: Routing Logic Specification 29 | internal static func acceptAlert(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 30 | if request.session == nil { 31 | return XCTestWDResponse.response(session: nil, error: WDStatus.SessionNotCreatedException) 32 | } else { 33 | let alert = XCTestWDAlert(request.session!.application) 34 | if alert.accept() { 35 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) accepAlert success") 36 | return XCTestWDResponse.response(session: request.session!, value: nil) 37 | } else { 38 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) accepAlert failure") 39 | return XCTestWDResponse.response(session: request.session!, error: WDStatus.NoAlertOpenError) 40 | } 41 | } 42 | } 43 | 44 | internal static func dismissAlert(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 45 | if request.session == nil { 46 | return XCTestWDResponse.response(session: nil, error: WDStatus.SessionNotCreatedException) 47 | } else { 48 | let alert = XCTestWDAlert(request.session!.application) 49 | if alert.dismiss() { 50 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) dismissAlert success") 51 | return XCTestWDResponse.response(session: request.session!, value: nil) 52 | } else { 53 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) dismissAlert failure") 54 | return XCTestWDResponse.response(session: request.session!, error: WDStatus.NoAlertOpenError) 55 | } 56 | } 57 | } 58 | 59 | internal static func alertText(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 60 | if request.session == nil { 61 | return XCTestWDResponse.response(session: nil, error: WDStatus.SessionNotCreatedException) 62 | } else { 63 | let alert = XCTestWDAlert(request.session!.application) 64 | let text = alert.text() 65 | if text != nil { 66 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) retrieving alert text \(text!)") 67 | return XCTestWDResponse.response(session: request.session!, value: JSON(text!)) 68 | } else { 69 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) retrieving alert text nil") 70 | return XCTestWDResponse.response(session: request.session!, error: WDStatus.NoAlertOpenError) 71 | } 72 | } 73 | } 74 | 75 | internal static func alertKeys(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 76 | if request.session == nil { 77 | return XCTestWDResponse.response(session: nil, error: WDStatus.SessionNotCreatedException) 78 | } else { 79 | let alert = XCTestWDAlert(request.session!.application) 80 | if alert.keys(input: request.params["text"] ?? "") { 81 | return XCTestWDResponse.response(session: request.session!, value: JSON(text!)) 82 | } else { 83 | return XCTestWDResponse.response(session: request.session!, error: WDStatus.NoAlertOpenError) 84 | } 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/_XCTestCaseImplementation.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | @class NSArray, NSInvocation, NSMutableArray, NSMutableDictionary, NSMutableSet, NSString, XCTestCaseRun, XCTestContext, XCTestExpectationWaiter, XCTWaiter; 8 | 9 | #import "CDStructures.h" 10 | 11 | @interface _XCTestCaseImplementation : NSObject 12 | { 13 | NSInvocation *_invocation; 14 | XCTestCaseRun *_testCaseRun; 15 | BOOL _continueAfterFailure; 16 | NSMutableSet *_expectations; 17 | NSArray *_activePerformanceMetricIDs; 18 | NSMutableDictionary *_perfMetricsForID; 19 | unsigned long long _startWallClockTime; 20 | struct time_value _startUserTime; 21 | struct time_value _startSystemTime; 22 | unsigned long long _measuringIteration; 23 | BOOL _isMeasuringMetrics; 24 | BOOL _didMeasureMetrics; 25 | BOOL _didStartMeasuring; 26 | BOOL _didStopMeasuring; 27 | NSString *_filePathForUnexpectedFailure; 28 | unsigned long long _lineNumberForUnexpectedFailure; 29 | unsigned long long _callAddressForCurrentWait; 30 | NSArray *_callAddressesForLastCreatedExpectation; 31 | long long _runLoopNestingCount; 32 | XCTWaiter *_currentWaiter; 33 | NSMutableArray *_failureRecords; 34 | BOOL _shouldHaltWhenReceivesControl; 35 | BOOL _shouldIgnoreSubsequentFailures; 36 | NSMutableArray *_activityRecordStack; 37 | XCTestContext *_testContext; 38 | } 39 | 40 | @property(readonly) XCTestContext *testContext; // @synthesize testContext=_testContext; 41 | @property(retain, nonatomic) XCTWaiter *currentWaiter; // @synthesize currentWaiter=_currentWaiter; 42 | @property(retain, nonatomic) NSMutableArray *activityRecordStack; // @synthesize activityRecordStack=_activityRecordStack; 43 | @property BOOL shouldIgnoreSubsequentFailures; // @synthesize shouldIgnoreSubsequentFailures=_shouldIgnoreSubsequentFailures; 44 | @property BOOL shouldHaltWhenReceivesControl; // @synthesize shouldHaltWhenReceivesControl=_shouldHaltWhenReceivesControl; 45 | @property(retain, nonatomic) NSMutableArray *failureRecords; // @synthesize failureRecords=_failureRecords; 46 | @property long long runLoopNestingCount; // @synthesize runLoopNestingCount=_runLoopNestingCount; 47 | @property(copy) NSArray *callAddressesForLastCreatedExpectation; // @synthesize callAddressesForLastCreatedExpectation=_callAddressesForLastCreatedExpectation; 48 | @property unsigned long long callAddressForCurrentWait; // @synthesize callAddressForCurrentWait=_callAddressForCurrentWait; 49 | @property unsigned long long lineNumberForUnexpectedFailure; // @synthesize lineNumberForUnexpectedFailure=_lineNumberForUnexpectedFailure; 50 | @property(copy) NSString *filePathForUnexpectedFailure; // @synthesize filePathForUnexpectedFailure=_filePathForUnexpectedFailure; 51 | @property(retain, nonatomic) NSMutableSet *expectations; // @synthesize expectations=_expectations; 52 | @property BOOL didStopMeasuring; // @synthesize didStopMeasuring=_didStopMeasuring; 53 | @property BOOL didStartMeasuring; // @synthesize didStartMeasuring=_didStartMeasuring; 54 | @property BOOL didMeasureMetrics; // @synthesize didMeasureMetrics=_didMeasureMetrics; 55 | @property BOOL isMeasuringMetrics; // @synthesize isMeasuringMetrics=_isMeasuringMetrics; 56 | @property unsigned long long measuringIteration; // @synthesize measuringIteration=_measuringIteration; 57 | @property struct time_value startUserTime; // @synthesize startUserTime=_startUserTime; 58 | @property struct time_value startSystemTime; // @synthesize startSystemTime=_startSystemTime; 59 | @property unsigned long long startWallClockTime; // @synthesize startWallClockTime=_startWallClockTime; 60 | @property(retain) NSMutableDictionary *perfMetricsForID; // @synthesize perfMetricsForID=_perfMetricsForID; 61 | @property(copy) NSArray *activePerformanceMetricIDs; // @synthesize activePerformanceMetricIDs=_activePerformanceMetricIDs; 62 | @property BOOL continueAfterFailure; // @synthesize continueAfterFailure=_continueAfterFailure; 63 | @property(retain) XCTestCaseRun *testCaseRun; // @synthesize testCaseRun=_testCaseRun; 64 | @property(retain) NSInvocation *invocation; // @synthesize invocation=_invocation; 65 | 66 | - (void)resetExpectations; 67 | - (void)addExpectation:(id)arg1; 68 | - (id)init; 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/Extensions/XCTestWDXPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDXPath.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 5/5/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AEXML 11 | import Fuzi 12 | 13 | internal class XCTestWDXPath { 14 | 15 | //MARK: External API 16 | static let defaultTopDir = "top" 17 | 18 | 19 | static func findMatchesIn(_ root:XCElementSnapshot, _ xpathQuery:String) -> [XCElementSnapshot]? { 20 | 21 | var mapping = [String:XCElementSnapshot]() 22 | let documentXml = generateXMLPresentation(root, 23 | nil, 24 | nil, 25 | defaultTopDir, 26 | &mapping)?.xml 27 | 28 | if documentXml == nil { 29 | return nil 30 | } 31 | 32 | let document = try? XMLDocument(string: documentXml!, encoding:String.Encoding.utf8) 33 | let nodes = document?.xpath(xpathQuery) 34 | var results = [XCElementSnapshot]() 35 | 36 | for node in nodes! { 37 | if mapping[node.attr("private_indexPath")!] != nil { 38 | results.append(mapping[node.attr("private_indexPath")!]!) 39 | } 40 | } 41 | 42 | return results 43 | } 44 | 45 | //MARK: Internal Utils 46 | static func generateXMLPresentation(_ root:XCElementSnapshot, _ parentElement:AEXMLElement?, _ writingDocument:AEXMLDocument?, _ indexPath:String, _ mapping: inout [String:XCElementSnapshot]) -> AEXMLDocument? { 47 | 48 | let elementName = XCUIElementTypeTransformer.singleton.stringWithElementType(root.elementType) 49 | let currentElement = AEXMLElement(name:elementName) 50 | recordAttributeForElement(root, currentElement, indexPath) 51 | 52 | let document : AEXMLDocument! 53 | if parentElement == nil || writingDocument == nil { 54 | document = AEXMLDocument() 55 | document.addChild(currentElement) 56 | } else { 57 | document = writingDocument! 58 | parentElement?.addChild(currentElement) 59 | } 60 | 61 | var index = 0; 62 | for child in root.children { 63 | let childSnapshot = child as! XCElementSnapshot 64 | let childIndexPath = indexPath.appending(",\(index)") 65 | index += 1 66 | mapping[childIndexPath] = childSnapshot 67 | 68 | _ = generateXMLPresentation(childSnapshot, currentElement, document, childIndexPath, &mapping) 69 | } 70 | 71 | return document 72 | } 73 | 74 | static func recordAttributeForElement(_ snapshot:XCElementSnapshot, _ currentElement:AEXMLElement, _ indexPath:String?) { 75 | 76 | currentElement.attributes["type"] = XCUIElementTypeTransformer.singleton.stringWithElementType(snapshot.elementType) 77 | 78 | if snapshot.wdValue() != nil { 79 | let value = snapshot.wdValue()! 80 | if let str = value as? String { 81 | currentElement.attributes["value"] = str 82 | } else if let bin = value as? Bool { 83 | currentElement.attributes["value"] = bin ? "1":"0"; 84 | } else { 85 | currentElement.attributes["value"] = (value as AnyObject).debugDescription 86 | } 87 | } 88 | 89 | if snapshot.wdName() != nil { 90 | currentElement.attributes["name"] = snapshot.wdName()! 91 | } 92 | 93 | if snapshot.wdLabel() != nil { 94 | currentElement.attributes["label"] = snapshot.wdLabel()! 95 | } 96 | 97 | currentElement.attributes["enabled"] = snapshot.isWDEnabled() ? "true":"false" 98 | 99 | let rect = snapshot.wdRect() 100 | for key in ["x","y","width","height"] { 101 | currentElement.attributes[key] = rect[key]!.description 102 | } 103 | 104 | if indexPath != nil { 105 | currentElement.attributes["private_indexPath"] = indexPath! 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Controllers/XCTestWDSessionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestAlertViewCommand.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import XCTest 12 | import SwiftyJSON 13 | import CocoaLumberjackSwift 14 | 15 | let XCTestWDSessionShutDown = "XCTestWDSessionShutDown" 16 | 17 | internal class XCTestWDSessionController: Controller { 18 | 19 | //MARK: Controller - Protocol 20 | static func routes() -> [(RequestRoute, RoutingCall)] { 21 | return [(RequestRoute("/wd/hub/session", "post"), createSession), 22 | (RequestRoute("/wd/hub/sessions", "get"), getSessions), 23 | (RequestRoute("/wd/hub/session/:sessionId", "delete"), delSession)] 24 | } 25 | 26 | static func shouldRegisterAutomatically() -> Bool { 27 | return false 28 | } 29 | 30 | //MARK: Routing Logic Specification 31 | internal static func createSession(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 32 | var app : XCUIApplication! 33 | var session : XCTestWDSession! 34 | 35 | let desiredCapabilities = request.jsonBody["desiredCapabilities"].dictionary 36 | let path = desiredCapabilities?["app"]?.string ?? nil 37 | let bundleID = desiredCapabilities?["bundleId"]?.string ?? nil 38 | 39 | if bundleID == nil { 40 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) bundle ID input is nil, create session with current active app") 41 | app = XCTestWDSession.activeApplication() 42 | } else { 43 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) create bundle from launching input") 44 | app = XCUIApplication.init(privateWithPath: path, bundleID: bundleID)! 45 | app!.launchArguments = desiredCapabilities?["arguments"]?.arrayObject as! [String]? ?? [String]() 46 | app!.launchEnvironment = desiredCapabilities?["environment"]?.dictionaryObject as! [String : String]? ?? [String:String](); 47 | app!.launch() 48 | sleep(1) 49 | } 50 | 51 | if app != nil { 52 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) create app failure ") 53 | session = XCTestWDSession.sessionWithApplication(app!) 54 | XCTestWDSessionManager.singleton.defaultSession = session; 55 | XCTestWDSessionManager.singleton.mountSession(session) 56 | session.resolve() 57 | } 58 | 59 | if app?.processID == 0 { 60 | return HttpResponse.internalServerError 61 | } 62 | 63 | return XCTestWDResponse.response(session: session, value: sessionInformation(session)) 64 | } 65 | 66 | internal static func getSessions(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 67 | return XCTestWDResponse.response(session: nil, value: sessionList()) 68 | } 69 | 70 | internal static func delSession(request: Swifter.HttpRequest) -> Swifter.HttpResponse { 71 | return XCTestWDResponse.response(session: nil, value: removeSessionById(request.session?.identifier ?? "")) 72 | } 73 | 74 | //MARK: Response helpers 75 | private static func sessionInformation(_ session:XCTestWDSession) -> JSON { 76 | var result:JSON = ["sessionId":session.identifier] 77 | var capabilities:JSON = ["device": UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad ? "ipad" : "iphone"] 78 | capabilities["sdkVersion"] = JSON(UIDevice.current.systemVersion) 79 | capabilities["browserName"] = JSON(session.application.label) 80 | capabilities["CFBundleIdentifier"] = JSON(session.application.bundleID ?? "Null") 81 | result["capabilities"] = capabilities 82 | return result 83 | } 84 | 85 | private static func sessionList() -> JSON { 86 | var raw = [[String:String]]() 87 | let sessionMap = XCTestWDSessionManager.singleton.queryAll() 88 | for (sessionId, _) in sessionMap { 89 | raw.append(["id":sessionId]) 90 | } 91 | return JSON(raw) 92 | } 93 | 94 | private static func removeSessionById(_ sessionId:String) -> JSON { 95 | XCTestWDSessionManager.singleton.deleteSession(sessionId) 96 | return JSON("") 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTRunnerDaemonSession.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "XCTestManager_TestsInterface-Protocol.h" 8 | #import "CDStructures.h" 9 | #import 10 | 11 | @class NSMutableDictionary, NSXPCConnection; 12 | @protocol XCTUIApplicationMonitor, XCTAXClient; 13 | 14 | // iOS since 10.3 15 | @interface XCTRunnerDaemonSession : NSObject 16 | { 17 | NSObject *_queue; 18 | id _applicationMonitor; 19 | id _accessibilityClient; 20 | NSXPCConnection *_connection; 21 | unsigned long long _daemonProtocolVersion; 22 | NSMutableDictionary *_invalidationHandlers; 23 | } 24 | @property(retain) NSObject *queue; // @synthesize queue=_queue; 25 | @property id accessibilityClient; // @synthesize accessibilityClient=_accessibilityClient; 26 | @property id applicationMonitor; // @synthesize applicationMonitor=_applicationMonitor; 27 | @property(retain) NSMutableDictionary *invalidationHandlers; // @synthesize invalidationHandlers=_invalidationHandlers; 28 | @property(retain) NSXPCConnection *connection; // @synthesize connection=_connection; 29 | @property(readonly) BOOL useLegacyEventCoordinateTransformationPath; 30 | @property unsigned long long daemonProtocolVersion; 31 | @property(readonly) id daemonProxy; 32 | 33 | + (instancetype)sharedSession; 34 | 35 | - (void)injectVoiceRecognitionAudioInputPaths:(id)arg1 completion:(CDUnknownBlockType)arg2; 36 | - (void)injectAssistantRecognitionStrings:(id)arg1 completion:(CDUnknownBlockType)arg2; 37 | - (void)startSiriUIRequestWithAudioFileURL:(id)arg1 completion:(CDUnknownBlockType)arg2; 38 | - (void)startSiriUIRequestWithText:(id)arg1 completion:(CDUnknownBlockType)arg2; 39 | - (void)requestDTServiceHubConnectionWithReply:(CDUnknownBlockType)arg1; 40 | - (void)enableFauxCollectionViewCells:(CDUnknownBlockType)arg1; 41 | - (void)loadAccessibilityWithTimeout:(double)arg1 reply:(CDUnknownBlockType)arg2; 42 | - (void)setAXTimeout:(double)arg1 reply:(CDUnknownBlockType)arg2; 43 | - (void)requestScreenshotWithReply:(CDUnknownBlockType)arg1; 44 | - (void)sendString:(id)arg1 maximumFrequency:(unsigned long long)arg2 completion:(CDUnknownBlockType)arg3; 45 | - (void)updateDeviceOrientation:(long long)arg1 completion:(CDUnknownBlockType)arg2; 46 | - (void)performDeviceEvent:(id)arg1 completion:(CDUnknownBlockType)arg2; 47 | - (void)synthesizeEvent:(id)arg1 completion:(CDUnknownBlockType)arg2; 48 | - (void)requestElementAtPoint:(CGPoint)arg1 reply:(CDUnknownBlockType)arg2; 49 | - (void)fetchParameterizedAttributeForElement:(id)arg1 attribute:(id)arg2 parameter:(id)arg3 reply:(CDUnknownBlockType)arg4; 50 | - (void)setAttribute:(id)arg1 value:(id)arg2 element:(id)arg3 reply:(CDUnknownBlockType)arg4; 51 | - (void)fetchAttributesForElement:(id)arg1 attributes:(id)arg2 reply:(CDUnknownBlockType)arg3; 52 | - (void)snapshotForElement:(id)arg1 attributes:(id)arg2 parameters:(id)arg3 reply:(CDUnknownBlockType)arg4; 53 | - (void)terminateApplicationWithBundleID:(id)arg1 completion:(CDUnknownBlockType)arg2; 54 | - (void)performAccessibilityAction:(int)arg1 onElement:(id)arg2 value:(id)arg3 reply:(CDUnknownBlockType)arg4; 55 | - (void)unregisterForAccessibilityNotification:(int)arg1 registrationToken:(id)arg2 reply:(CDUnknownBlockType)arg3; 56 | - (void)registerForAccessibilityNotification:(int)arg1 reply:(CDUnknownBlockType)arg2; 57 | - (void)launchApplicationWithBundleID:(id)arg1 arguments:(id)arg2 environment:(id)arg3 completion:(CDUnknownBlockType)arg4; 58 | - (void)startMonitoringApplicationWithBundleID:(id)arg1; 59 | - (void)requestBackgroundAssertionForPID:(int)arg1 reply:(CDUnknownBlockType)arg2; 60 | - (void)requestAutomationSessionForTestTargetWithPID:(int)arg1 reply:(CDUnknownBlockType)arg2; 61 | - (void)requestIDEConnectionSocketForSessionIdentifier:(id)arg1 reply:(CDUnknownBlockType)arg2; 62 | - (void)_XCT_receivedAccessibilityNotification:(int)arg1 withPayload:(id)arg2; 63 | - (void)_XCT_applicationWithBundleID:(id)arg1 didUpdatePID:(int)arg2 andState:(unsigned long long)arg3; 64 | - (void)unregisterInvalidationHandlerWithToken:(id)arg1; 65 | - (id)registerInvalidationHandler:(CDUnknownBlockType)arg1; 66 | - (void)_reportInvalidation; 67 | - (id)initWithConnection:(id)arg1; 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCElementSnapshot.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import "CDStructures.h" 8 | 9 | #import 10 | 11 | @class NSArray, NSDictionary, NSString, XCAccessibilityElement, XCUIApplication; 12 | 13 | @interface XCElementSnapshot : NSObject 14 | { 15 | NSString *_identifier; 16 | id _value; 17 | NSString *_placeholderValue; 18 | BOOL _enabled; 19 | BOOL _selected; 20 | BOOL _isMainWindow; 21 | BOOL _hasKeyboardFocus; 22 | BOOL _hasFocus; 23 | XCUIApplication *_application; 24 | unsigned long long _generation; 25 | NSString *_title; 26 | NSString *_label; 27 | unsigned long long _elementType; 28 | long long _horizontalSizeClass; 29 | long long _verticalSizeClass; 30 | XCAccessibilityElement *_accessibilityElement; 31 | XCAccessibilityElement *_parentAccessibilityElement; 32 | XCElementSnapshot *_parent; 33 | NSArray *_children; 34 | unsigned long long _traits; 35 | NSArray *_userTestingAttributes; 36 | NSDictionary *_additionalAttributes; 37 | struct CGRect _frame; 38 | } 39 | @property BOOL hasFocus; // @synthesize hasFocus=_hasFocus; 40 | @property BOOL hasKeyboardFocus; // @synthesize hasKeyboardFocus=_hasKeyboardFocus; 41 | @property(copy) NSDictionary *additionalAttributes; // @synthesize additionalAttributes=_additionalAttributes; 42 | @property(copy) NSArray *userTestingAttributes; // @synthesize userTestingAttributes=_userTestingAttributes; 43 | @property unsigned long long traits; // @synthesize traits=_traits; 44 | @property BOOL isMainWindow; // @synthesize isMainWindow=_isMainWindow; 45 | @property(copy) NSArray *children; // @synthesize children=_children; 46 | @property XCElementSnapshot *parent; // @synthesize parent=_parent; 47 | @property(retain) XCAccessibilityElement *parentAccessibilityElement; // @synthesize parentAccessibilityElement=_parentAccessibilityElement; 48 | @property(retain) XCAccessibilityElement *accessibilityElement; // @synthesize accessibilityElement=_accessibilityElement; 49 | @property(readonly) NSArray *suggestedHitpoints; 50 | @property(readonly) struct CGRect visibleFrame; 51 | @property(readonly) XCElementSnapshot *scrollView; 52 | @property(readonly, copy) NSString *truncatedValueString; 53 | @property(readonly) long long depth; 54 | @property(readonly, copy) XCElementSnapshot *pathFromRoot; 55 | @property(readonly) BOOL isTopLevelTouchBarElement; 56 | @property(readonly) BOOL isTouchBarElement; 57 | @property(readonly, copy) NSString *sparseTreeDescription; 58 | @property(readonly, copy) NSString *compactDescription; 59 | @property(readonly, copy) NSString *pathDescription; 60 | @property(readonly) NSString *recursiveDescriptionIncludingAccessibilityElement; 61 | @property(readonly) NSString *recursiveDescription; 62 | @property(readonly, copy) NSArray *identifiers; 63 | @property(nonatomic) unsigned long long generation; // @synthesize generation=_generation; 64 | @property(nonatomic) XCUIApplication *application; // @synthesize application=_application; 65 | @property(readonly) struct CGPoint hitPointForScrolling; 66 | @property(readonly) struct CGPoint hitPoint; 67 | 68 | - (id)_uniquelyIdentifyingObjectiveCCode; 69 | - (id)_uniquelyIdentifyingSwiftCode; 70 | - (BOOL)_isAncestorOfElement:(id)arg1; 71 | - (BOOL)_isDescendantOfElement:(id)arg1; 72 | - (id)rootElement; 73 | - (BOOL)_frameFuzzyMatchesElement:(id)arg1; 74 | - (BOOL)_fuzzyMatchesElement:(id)arg1; 75 | - (BOOL)_matchesElement:(id)arg1; 76 | - (BOOL)matchesTreeWithRoot:(id)arg1; 77 | - (void)mergeTreeWithSnapshot:(id)arg1; 78 | - (id)_childMatchingElement:(id)arg1; 79 | - (NSArray *)_allDescendants; 80 | - (BOOL)hasDescendantMatchingFilter:(CDUnknownBlockType)arg1; 81 | - (NSArray *)descendantsByFilteringWithBlock:(BOOL(^)(XCElementSnapshot *snapshot))block; 82 | - (id)elementSnapshotMatchingAccessibilityElement:(id)arg1; 83 | - (void)enumerateDescendantsUsingBlock:(void(^)(XCElementSnapshot *snapshot))block; 84 | - (id)recursiveDescriptionWithIndent:(id)arg1 includeAccessibilityElement:(BOOL)arg2; 85 | - (id)init; 86 | - (struct CGPoint)hostingAndOrientationTransformedPoint:(struct CGPoint)arg1; 87 | - (struct CGPoint)_transformPoint:(struct CGPoint)arg1 windowContextID:(id)arg2 windowDisplayID:(id)arg3; 88 | - (id)hitTest:(struct CGPoint)arg1; 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/XCTestWDStatus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDStatus.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 24/4/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum WDStatus: Int { 12 | case 13 | Success = 0, 14 | NoSuchElement = 7, 15 | NoSuchFrame = 8, 16 | UnknownCommand = 9, 17 | StaleElementReference = 10, 18 | ElementNotVisible = 11, 19 | InvalidElementState = 12, 20 | UnknownError = 13, 21 | ElementIsNotSelectable = 15, 22 | JavaScriptError = 17, 23 | XPathLookupError = 19, 24 | Timeout = 21, 25 | NoSuchWindow = 23, 26 | InvalidCookieDomain = 24, 27 | UnableToSetCookie = 25, 28 | UnexpectedAlertOpen = 26, 29 | NoAlertOpenError = 27, 30 | ScriptTimeout = 28, 31 | InvalidElementCoordinates = 29, 32 | IMENotAvailable = 30, 33 | IMEEngineActivationFailed = 31, 34 | InvalidSelector = 32, 35 | SessionNotCreatedException = 33, 36 | MoveTargetOutOfBounds = 34 37 | 38 | static func evaluate(_ status:WDStatus) -> String { 39 | switch status { 40 | 41 | case .Success: 42 | return "The command executed successfully" 43 | 44 | case .NoSuchElement: 45 | return "An element could not be located on the page using the given search parameters." 46 | 47 | case .NoSuchFrame: 48 | return "A request to switch to a frame could not be satisfied because the frame could not be found." 49 | 50 | case .UnknownCommand: 51 | return "The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource." 52 | 53 | case .StaleElementReference: 54 | return "An element command failed because the referenced element is no longer attached to the DOM." 55 | 56 | case .ElementNotVisible: 57 | return "An element command could not be completed because the element is not visible on the page." 58 | 59 | case .InvalidElementState: 60 | return "An element command could not be completed because the element is in an invalid state (e.g. attempting to click a disabled element)." 61 | 62 | case .UnknownError: 63 | return "An unknown server-side error occurred while processing the command." 64 | 65 | case .ElementIsNotSelectable: 66 | return "An attempt was made to select an element that cannot be selected." 67 | 68 | case .JavaScriptError: 69 | return "An error occurred while executing user supplied JavaScript." 70 | 71 | case .XPathLookupError: 72 | return "An error occurred while searching for an element by XPath." 73 | 74 | case .Timeout: 75 | return "An operation did not complete before its timeout expired." 76 | 77 | case .NoSuchWindow: 78 | return "A request to switch to a different window could not be satisfied because the window could not be found." 79 | 80 | case .InvalidCookieDomain: 81 | return "An illegal attempt was made to set a cookie under a different domain than the current page." 82 | 83 | case .UnableToSetCookie: 84 | return "A request to set a cookie's value could not be satisfied." 85 | 86 | case .UnexpectedAlertOpen: 87 | return "A modal dialog was open, blocking this operation." 88 | 89 | case .NoAlertOpenError: 90 | return "An attempt was made to operate on a modal dialog when one was not open." 91 | 92 | case .ScriptTimeout: 93 | return "A script did not complete before its timeout expired." 94 | 95 | case .InvalidElementCoordinates: 96 | return "The coordinates provided to an interactions operation are invalid." 97 | 98 | case .IMENotAvailable: 99 | return "IME was not available." 100 | 101 | case .IMEEngineActivationFailed: 102 | return "An IME engine could not be started." 103 | 104 | case .InvalidSelector: 105 | return "Argument was an invalid selector (e.g. XPath/CSS)." 106 | 107 | case .SessionNotCreatedException: 108 | return "Session Not Created Exception" 109 | 110 | case .MoveTargetOutOfBounds: 111 | return "Move Target Out Of Bounds" 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true, 7 | "mocha": true 8 | }, 9 | "plugins": [ 10 | "mocha" 11 | ], 12 | // https://github.com/feross/eslint-config-standard 13 | "rules": { 14 | "accessor-pairs": 2, 15 | "block-scoped-var": 0, 16 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 17 | "camelcase": 0, 18 | "comma-dangle": [2, "never"], 19 | "comma-spacing": [2, { "before": false, "after": true }], 20 | "comma-style": [2, "last"], 21 | "complexity": 0, 22 | "consistent-return": 0, 23 | "consistent-this": 0, 24 | "curly": [2, "multi-line"], 25 | "default-case": 0, 26 | "dot-location": [2, "property"], 27 | "dot-notation": 0, 28 | "eol-last": 2, 29 | "eqeqeq": [2, "allow-null"], 30 | "func-names": 0, 31 | "func-style": 0, 32 | "generator-star-spacing": [2, "both"], 33 | "guard-for-in": 0, 34 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ], 35 | "indent": [2, 2], 36 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 37 | "linebreak-style": 0, 38 | "max-depth": 0, 39 | "max-len": 0, 40 | "max-nested-callbacks": 0, 41 | "max-params": 0, 42 | "max-statements": 0, 43 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 44 | "new-parens": 2, 45 | "no-alert": 0, 46 | "no-array-constructor": 2, 47 | "no-bitwise": 0, 48 | "no-caller": 2, 49 | "no-catch-shadow": 0, 50 | "no-cond-assign": 2, 51 | "no-console": 0, 52 | "no-constant-condition": 0, 53 | "no-continue": 0, 54 | "no-control-regex": 2, 55 | "no-debugger": 2, 56 | "no-delete-var": 2, 57 | "no-div-regex": 0, 58 | "no-dupe-args": 2, 59 | "no-dupe-keys": 2, 60 | "no-duplicate-case": 2, 61 | "no-else-return": 0, 62 | "no-empty": 0, 63 | "no-empty-character-class": 2, 64 | "no-eq-null": 0, 65 | "no-eval": 2, 66 | "no-ex-assign": 2, 67 | "no-extend-native": 2, 68 | "no-extra-bind": 2, 69 | "no-extra-boolean-cast": 2, 70 | "no-extra-semi": 0, 71 | "no-extra-strict": 0, 72 | "no-fallthrough": 2, 73 | "no-floating-decimal": 2, 74 | "no-func-assign": 2, 75 | "no-implied-eval": 2, 76 | "no-inline-comments": 0, 77 | "no-inner-declarations": [2, "functions"], 78 | "no-invalid-regexp": 2, 79 | "no-irregular-whitespace": 2, 80 | "no-iterator": 2, 81 | "no-label-var": 2, 82 | "no-labels": 2, 83 | "no-lone-blocks": 2, 84 | "no-lonely-if": 0, 85 | "no-loop-func": 0, 86 | "no-mixed-requires": 0, 87 | "no-mixed-spaces-and-tabs": [2, false], 88 | "no-multi-spaces": 2, 89 | "no-multi-str": 2, 90 | "no-multiple-empty-lines": [2, { "max": 1 }], 91 | "no-native-reassign": 2, 92 | "no-negated-in-lhs": 2, 93 | "no-nested-ternary": 0, 94 | "no-new": 0, 95 | "no-new-func": 2, 96 | "no-new-object": 2, 97 | "no-new-require": 2, 98 | "no-new-wrappers": 2, 99 | "no-obj-calls": 2, 100 | "no-octal": 2, 101 | "no-octal-escape": 2, 102 | "no-path-concat": 0, 103 | "no-plusplus": 0, 104 | "no-process-env": 0, 105 | "no-process-exit": 0, 106 | "no-proto": 2, 107 | "no-redeclare": 2, 108 | "no-regex-spaces": 2, 109 | "no-reserved-keys": 0, 110 | "no-restricted-modules": 0, 111 | "no-return-assign": 2, 112 | "no-script-url": 0, 113 | "no-self-compare": 2, 114 | "no-sequences": 2, 115 | "no-shadow": 0, 116 | "no-shadow-restricted-names": 2, 117 | "no-spaced-func": 2, 118 | "no-sparse-arrays": 2, 119 | "no-sync": 0, 120 | "no-ternary": 0, 121 | "no-throw-literal": 2, 122 | "no-trailing-spaces": 2, 123 | "no-undef": 2, 124 | "no-undef-init": 2, 125 | "no-undefined": 0, 126 | "no-underscore-dangle": 0, 127 | "no-unneeded-ternary": 2, 128 | "no-unreachable": 2, 129 | "no-unused-expressions": 0, 130 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 131 | "no-use-before-define": 0, 132 | "no-var": 0, 133 | "no-void": 0, 134 | "no-warning-comments": 0, 135 | "no-with": 2, 136 | "no-extra-parens": 0, 137 | "object-curly-spacing": 0, 138 | "one-var": [2, { "initialized": "never" }], 139 | "operator-assignment": 0, 140 | "operator-linebreak": [2, "after"], 141 | "padded-blocks": 0, 142 | "quote-props": 0, 143 | "quotes": [1, "single", "avoid-escape"], 144 | "radix": 2, 145 | "semi": [2, "always"], 146 | "semi-spacing": 0, 147 | "sort-vars": 0, 148 | "keyword-spacing": [2], 149 | "space-before-blocks": [2, "always"], 150 | "space-before-function-paren": 0, 151 | "space-in-parens": [2, "never"], 152 | "space-infix-ops": 2, 153 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 154 | "spaced-comment": [2, "always"], 155 | "strict": 0, 156 | "use-isnan": 2, 157 | "valid-jsdoc": 0, 158 | "valid-typeof": 2, 159 | "vars-on-top": 0, 160 | "wrap-iife": [2, "any"], 161 | "wrap-regex": 0, 162 | "yoda": [2, "never"] 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/XCUIElementTypeTransformer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCUIElementTypeTransformer.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 29/4/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class XCUIElementTypeTransformer { 12 | 13 | var elementStringMapping:[UInt:String] 14 | var stringElementMapping:[String:UInt] 15 | 16 | static let singleton = XCUIElementTypeTransformer() 17 | 18 | private init() { 19 | elementStringMapping = [ 20 | 0 : "XCUIElementTypeAny", 21 | 1 : "XCUIElementTypeOther", 22 | 2 : "XCUIElementTypeApplication", 23 | 3 : "XCUIElementTypeGroup", 24 | 4 : "XCUIElementTypeWindow", 25 | 5 : "XCUIElementTypeSheet", 26 | 6 : "XCUIElementTypeDrawer", 27 | 7 : "XCUIElementTypeAlert", 28 | 8 : "XCUIElementTypeDialog", 29 | 9 : "XCUIElementTypeButton", 30 | 10 : "XCUIElementTypeRadioButton", 31 | 11 : "XCUIElementTypeRadioGroup", 32 | 12 : "XCUIElementTypeCheckBox", 33 | 13 : "XCUIElementTypeDisclosureTriangle", 34 | 14 : "XCUIElementTypePopUpButton", 35 | 15 : "XCUIElementTypeComboBox", 36 | 16 : "XCUIElementTypeMenuButton", 37 | 17 : "XCUIElementTypeToolbarButton", 38 | 18 : "XCUIElementTypePopover", 39 | 19 : "XCUIElementTypeKeyboard", 40 | 20 : "XCUIElementTypeKey", 41 | 21 : "XCUIElementTypeNavigationBar", 42 | 22 : "XCUIElementTypeTabBar", 43 | 23 : "XCUIElementTypeTabGroup", 44 | 24 : "XCUIElementTypeToolbar", 45 | 25 : "XCUIElementTypeStatusBar", 46 | 26 : "XCUIElementTypeTable", 47 | 27 : "XCUIElementTypeTableRow", 48 | 28 : "XCUIElementTypeTableColumn", 49 | 29 : "XCUIElementTypeOutline", 50 | 30 : "XCUIElementTypeOutlineRow", 51 | 31 : "XCUIElementTypeBrowser", 52 | 32 : "XCUIElementTypeCollectionView", 53 | 33 : "XCUIElementTypeSlider", 54 | 34 : "XCUIElementTypePageIndicator", 55 | 35 : "XCUIElementTypeProgressIndicator", 56 | 36 : "XCUIElementTypeActivityIndicator", 57 | 37 : "XCUIElementTypeSegmentedControl", 58 | 38 : "XCUIElementTypePicker", 59 | 39 : "XCUIElementTypePickerWheel", 60 | 40 : "XCUIElementTypeSwitch", 61 | 41 : "XCUIElementTypeToggle", 62 | 42 : "XCUIElementTypeLink", 63 | 43 : "XCUIElementTypeImage", 64 | 44 : "XCUIElementTypeIcon", 65 | 45 : "XCUIElementTypeSearchField", 66 | 46 : "XCUIElementTypeScrollView", 67 | 47 : "XCUIElementTypeScrollBar", 68 | 48 : "XCUIElementTypeStaticText", 69 | 49 : "XCUIElementTypeTextField", 70 | 50 : "XCUIElementTypeSecureTextField", 71 | 51 : "XCUIElementTypeDatePicker", 72 | 52 : "XCUIElementTypeTextView", 73 | 53 : "XCUIElementTypeMenu", 74 | 54 : "XCUIElementTypeMenuItem", 75 | 55 : "XCUIElementTypeMenuBar", 76 | 56 : "XCUIElementTypeMenuBarItem", 77 | 57 : "XCUIElementTypeMap", 78 | 58 : "XCUIElementTypeWebView", 79 | 59 : "XCUIElementTypeIncrementArrow", 80 | 60 : "XCUIElementTypeDecrementArrow", 81 | 61 : "XCUIElementTypeTimeline", 82 | 62 : "XCUIElementTypeRatingIndicator", 83 | 63 : "XCUIElementTypeValueIndicator", 84 | 64 : "XCUIElementTypeSplitGroup", 85 | 65 : "XCUIElementTypeSplitter", 86 | 66 : "XCUIElementTypeRelevanceIndicator", 87 | 67 : "XCUIElementTypeColorWell", 88 | 68 : "XCUIElementTypeHelpTag", 89 | 69 : "XCUIElementTypeMatte", 90 | 70 : "XCUIElementTypeDockItem", 91 | 71 : "XCUIElementTypeRuler", 92 | 72 : "XCUIElementTypeRulerMarker", 93 | 73 : "XCUIElementTypeGrid", 94 | 74 : "XCUIElementTypeLevelIndicator", 95 | 75 : "XCUIElementTypeCell", 96 | 76 : "XCUIElementTypeLayoutArea", 97 | 77 : "XCUIElementTypeLayoutItem", 98 | 78 : "XCUIElementTypeHandle", 99 | 79 : "XCUIElementTypeStepper", 100 | 80 : "XCUIElementTypeTab"] 101 | 102 | stringElementMapping = [String:UInt]() 103 | for (key, value) in elementStringMapping { 104 | stringElementMapping[value] = key 105 | } 106 | } 107 | 108 | func elementTypeWithTypeName(_ typeName:String) -> XCUIElement.ElementType { 109 | return XCUIElement.ElementType(rawValue: stringElementMapping[typeName]!)! 110 | } 111 | 112 | func stringWithElementType(_ elementType:XCUIElement.ElementType) -> String { 113 | return elementStringMapping[elementType.rawValue]! 114 | } 115 | 116 | func shortStringWithElementType(_ elementType:XCUIElement.ElementType) -> String { 117 | return stringWithElementType(elementType).replacingOccurrences(of: "XCUIElementType", with: "") 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/PrivateHeaders/XCTestCase.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by class-dump 3.5 (64 bit). 3 | // 4 | // class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard. 5 | // 6 | 7 | #import 8 | 9 | #import "CDStructures.h" 10 | 11 | @class NSInvocation, XCTestCaseRun, XCTestContext, _XCTestCaseImplementation; 12 | 13 | @interface XCTestCase() 14 | { 15 | id _internalImplementation; 16 | } 17 | @property(retain) _XCTestCaseImplementation *internalImplementation; // @synthesize internalImplementation=_internalImplementation; 18 | @property(readonly) XCTestContext *testContext; 19 | @property(readonly) unsigned long long activityRecordStackDepth; 20 | @property(nonatomic) BOOL shouldHaltWhenReceivesControl; 21 | @property(nonatomic) BOOL shouldSetShouldHaltWhenReceivesControl; // @synthesize shouldSetShouldHaltWhenReceivesControl=_shouldSetShouldHaltWhenReceivesControl; 22 | @property(retain) XCTestCaseRun *testCaseRun; 23 | 24 | + (id)_baselineDictionary; 25 | + (BOOL)_treatMissingBaselinesAsTestFailures; 26 | + (id)knownMemoryMetrics; 27 | + (id)measurementFormatter; 28 | + (BOOL)_reportPerformanceFailuresForLargeImprovements; 29 | + (BOOL)_enableSymbolication; 30 | 31 | + (BOOL)isInheritingTestCases; 32 | + (id)_testStartActvityDateFormatter; 33 | + (id)testCaseWithSelector:(SEL)arg1; 34 | 35 | 36 | + (void)tearDown; 37 | + (void)setUp; 38 | + (id)defaultTestSuite; 39 | + (id)allTestMethodInvocations; 40 | + (void)_allTestMethodInvocations:(id)arg1; 41 | + (id)testMethodInvocations; 42 | + (id)allSubclasses; 43 | - (void)startActivityWithTitle:(id)arg1 block:(CDUnknownBlockType)arg2; 44 | - (void)registerDefaultMetrics; 45 | - (id)baselinesDictionaryForTest; 46 | - (void)_logAndReportPerformanceMetrics:(id)arg1 perfMetricResultsForIDs:(id)arg2 withBaselinesForTest:(id)arg3; 47 | - (void)_logAndReportPerformanceMetrics:(id)arg1 perfMetricResultsForIDs:(id)arg2 withBaselinesForTest:(id)arg3 defaultBaselinesForPerfMetricID:(id)arg4; 48 | - (void)registerMetricID:(id)arg1 name:(id)arg2 unitString:(id)arg3; 49 | - (void)registerMetricID:(id)arg1 name:(id)arg2 unit:(id)arg3; 50 | - (void)reportMetric:(id)arg1 reportFailures:(BOOL)arg2; 51 | - (void)reportMeasurements:(id)arg1 forMetricID:(id)arg2 reportFailures:(BOOL)arg3; 52 | - (void)_recordValues:(id)arg1 forPerformanceMetricID:(id)arg2 name:(id)arg3 unitsOfMeasurement:(id)arg4 baselineName:(id)arg5 baselineAverage:(id)arg6 maxPercentRegression:(id)arg7 maxPercentRelativeStandardDeviation:(id)arg8 maxRegression:(id)arg9 maxStandardDeviation:(id)arg10 file:(id)arg11 line:(unsigned long long)arg12; 53 | - (id)_symbolicationRecordForTestCodeInAddressStack:(id)arg1; 54 | - (void)measureBlock:(CDUnknownBlockType)arg1; 55 | - (void)stopMeasuring; 56 | - (void)startMeasuring; 57 | - (BOOL)_isMeasuringMetrics; 58 | - (BOOL)_didStopMeasuring; 59 | - (BOOL)_didStartMeasuring; 60 | - (BOOL)_didMeasureMetrics; 61 | - (id)_perfMetricsForID; 62 | - (void)_logMemoryGraphDataFromFilePath:(id)arg1 withTitle:(id)arg2; 63 | - (void)_logMemoryGraphData:(id)arg1 withTitle:(id)arg2; 64 | - (unsigned long long)numberOfTestIterationsForTestWithSelector:(SEL)arg1; 65 | - (void)afterTestIteration:(unsigned long long)arg1 selector:(SEL)arg2; 66 | - (void)beforeTestIteration:(unsigned long long)arg1 selector:(SEL)arg2; 67 | - (void)tearDownTestWithSelector:(SEL)arg1; 68 | - (void)setUpTestWithSelector:(SEL)arg1; 69 | - (void)performTest:(id)arg1; 70 | - (void)invokeTest; 71 | - (Class)testRunClass; 72 | - (Class)_requiredTestRunBaseClass; 73 | - (void)_recordUnexpectedFailureWithDescription:(id)arg1 error:(id)arg2; 74 | - (void)_recordUnexpectedFailureWithDescription:(id)arg1 exception:(id)arg2; 75 | - (void)_enqueueFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected; 76 | - (void)_dequeueFailures; 77 | - (void)_interruptTest; 78 | - (BOOL)isEqual:(id)arg1; 79 | - (id)nameForLegacyLogging; 80 | - (id)name; 81 | - (id)languageAgnosticTestMethodName; 82 | - (unsigned long long)testCaseCount; 83 | - (id)initWithSelector:(SEL)arg1; 84 | - (id)init; 85 | - (void)waiter:(id)arg1 didFulfillInvertedExpectation:(id)arg2; 86 | - (void)waiter:(id)arg1 fulfillmentDidViolateOrderingConstraintsForExpectation:(id)arg2 requiredExpectation:(id)arg3; 87 | - (void)waiter:(id)arg1 didTimeoutWithUnfulfilledExpectations:(id)arg2; 88 | - (id)expectationForPredicate:(id)arg1 evaluatedWithObject:(id)arg2 handler:(CDUnknownBlockType)arg3; 89 | - (id)expectationForNotification:(id)arg1 object:(id)arg2 handler:(CDUnknownBlockType)arg3; 90 | - (id)keyValueObservingExpectationForObject:(id)arg1 keyPath:(id)arg2 handler:(CDUnknownBlockType)arg3; 91 | - (id)keyValueObservingExpectationForObject:(id)arg1 keyPath:(id)arg2 expectedValue:(id)arg3; 92 | - (void)_addExpectation:(id)arg1; 93 | - (void)waitForExpectations:(id)arg1 timeout:(double)arg2 enforceOrder:(BOOL)arg3; 94 | - (void)waitForExpectations:(id)arg1 timeout:(double)arg2; 95 | - (void)waitForExpectationsWithTimeout:(double)arg1 handler:(CDUnknownBlockType)arg2; 96 | - (void)_waitForExpectations:(id)arg1 timeout:(double)arg2 enforceOrder:(BOOL)arg3 handler:(CDUnknownBlockType)arg4; 97 | - (id)expectationWithDescription:(id)arg1; 98 | - (id)_expectationForDarwinNotification:(id)arg1; 99 | - (void)nestedWaiter:(id)arg1 wasInterruptedByTimedOutWaiter:(id)arg2; 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/XCTestWDSession.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWDSession.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 23/4/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import SwiftyJSON 12 | import CocoaLumberjackSwift 13 | 14 | //MARK: Session & Cache for XCUIElement 15 | internal class XCTestWDElementCache 16 | { 17 | 18 | private var cache = [String: XCUIElement]() 19 | 20 | // Returns UUID of the stored element 21 | func storeElement(_ element:XCUIElement) -> String { 22 | let uuid = UUID.init().uuidString 23 | cache[uuid] = element 24 | return uuid 25 | } 26 | 27 | // Returns cached element 28 | func elementForUUID(_ uuid:String?) -> XCUIElement? { 29 | if uuid == nil { 30 | return nil 31 | } 32 | return cache[uuid!] 33 | } 34 | } 35 | 36 | internal class XCTestWDSession { 37 | 38 | var identifier: String! 39 | private var _application: XCUIApplication! 40 | var application: XCUIApplication! { 41 | get { 42 | // Add protection for application resolve. only when application status active cam execute this 43 | if _application.accessibilityActivate() == true { 44 | resolve() 45 | } 46 | return _application 47 | } 48 | set { 49 | _application = newValue 50 | } 51 | } 52 | 53 | static func sessionWithApplication(_ application: XCUIApplication) -> XCTestWDSession { 54 | 55 | let session = XCTestWDSession() 56 | session.application = application 57 | session.identifier = UUID.init().uuidString 58 | 59 | return session 60 | } 61 | 62 | static func activeApplication() -> XCUIApplication? 63 | { 64 | return XCTestWDApplication.activeApplication() 65 | } 66 | 67 | func resolve() { 68 | self._application.query() 69 | self._application.fb_nativeResolve() 70 | } 71 | } 72 | 73 | //MARK: Multi-Session Control 74 | internal class XCTestWDSessionManager { 75 | 76 | static let singleton = XCTestWDSessionManager() 77 | static let commonCache: XCTestWDElementCache = XCTestWDElementCache() 78 | 79 | private var sessionMapping = [String: XCTestWDSession]() 80 | var defaultSession:XCTestWDSession? 81 | 82 | func mountSession(_ session: XCTestWDSession) { 83 | sessionMapping[session.identifier] = session 84 | } 85 | 86 | func querySession(_ identifier:String) -> XCTestWDSession? { 87 | return sessionMapping[identifier] 88 | } 89 | 90 | func checkDefaultSession() -> XCTestWDSession { 91 | if self.defaultSession == nil || self.defaultSession?.application.state != XCUIApplication.State.runningForeground { 92 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) current application not active, reloading active application") 93 | sleep(3) 94 | let application = XCTestWDSession.activeApplication() 95 | self.defaultSession = XCTestWDSession.sessionWithApplication(application!) 96 | self.defaultSession?.resolve() 97 | } 98 | 99 | return self.defaultSession! 100 | } 101 | 102 | func queryAll() -> [String:XCTestWDSession] { 103 | return sessionMapping 104 | } 105 | 106 | func clearAll() { 107 | sessionMapping.removeAll() 108 | } 109 | 110 | func deleteSession(_ sessionId:String) { 111 | sessionMapping.removeValue(forKey: sessionId) 112 | NotificationCenter.default.post(name: NSNotification.Name(XCTestWDSessionShutDown), object: nil) 113 | } 114 | } 115 | 116 | //MARK: Extension 117 | extension HttpRequest { 118 | var session: XCTestWDSession? { 119 | get { 120 | if self.params["sessionId"] != nil && XCTestWDSessionManager.singleton.querySession(self.params["sessionId"]!) != nil { 121 | return XCTestWDSessionManager.singleton.querySession(self.params["sessionId"]!) 122 | } else if self.path.contains("/session/") { 123 | let components = self.path.components(separatedBy:"/") 124 | let index = components.index(of: "session")! 125 | if index >= components.count - 1 { 126 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) session can't be at the last component in the whole path") 127 | return nil 128 | } 129 | return XCTestWDSessionManager.singleton.querySession(components[index + 1]) 130 | } else { 131 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) no session id in current request") 132 | return nil 133 | } 134 | } 135 | } 136 | 137 | var elementId: String? { 138 | get { 139 | if self.path.contains("/element/") { 140 | let components = self.path.components(separatedBy:"/") 141 | let index = components.index(of: "element")! 142 | if index < components.count - 1 { 143 | return components[index + 1] 144 | } 145 | } 146 | 147 | DDLogDebug("\(XCTestWDDebugInfo.DebugLogPrefix) no element id retrieved from current query") 148 | return nil 149 | } 150 | } 151 | 152 | var jsonBody:JSON { 153 | get { 154 | return (try? JSON(data: NSData(bytes: &self.body, length: self.body.count) as Data)) ?? JSON(parseJSON: "{}") 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /scripts/install.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const xcode = require('xcode'); 6 | const _ = require('macaca-utils'); 7 | const shelljs = require('shelljs'); 8 | const hostname = require('os').hostname(); 9 | const doctorIOS = require('macaca-doctor/lib/ios'); 10 | 11 | if (!_.platform.isOSX) { 12 | return; 13 | } 14 | 15 | const DEVELOPMENT_TEAM = process.env.DEVELOPMENT_TEAM_ID || ''; 16 | 17 | const xctestwdFrameworksPrefix = 'xctestwd-frameworks'; 18 | 19 | const update = function (project, schemeName, callback) { 20 | const myConfigKey = project.pbxTargetByName(schemeName).buildConfigurationList; 21 | const buildConfig = project.pbxXCConfigurationList()[myConfigKey]; 22 | const configArray = buildConfig.buildConfigurations; 23 | const keys = configArray.map(item => item.value); 24 | const pbxXCBuildConfigurationSection = project.pbxXCBuildConfigurationSection(); 25 | keys.forEach(key => { 26 | callback(pbxXCBuildConfigurationSection[key].buildSettings); 27 | }); 28 | }; 29 | 30 | const updateInformation = function () { 31 | try { 32 | const schemeName = 'XCTestWDUITests'; 33 | const projectPath = path.join(__dirname, '..', 'XCTestWD', 'XCTestWD.xcodeproj', 'project.pbxproj'); 34 | const myProj = xcode.project(projectPath); 35 | myProj.parseSync(); 36 | 37 | update(myProj, schemeName, function (buildSettings) { 38 | const newBundleId = process.env.BUNDLE_ID || `XCTestWDRunner.XCTestWDRunner.${hostname}`; 39 | buildSettings.PRODUCT_BUNDLE_IDENTIFIER = newBundleId; 40 | if (DEVELOPMENT_TEAM) { 41 | buildSettings.DEVELOPMENT_TEAM = DEVELOPMENT_TEAM; 42 | } 43 | }); 44 | 45 | const projSect = myProj.getFirstProject(); 46 | const myRunnerTargetKey = myProj.findTargetKey(schemeName); 47 | const targetAttributes = projSect.firstProject.attributes.TargetAttributes; 48 | const runnerObj = targetAttributes[myRunnerTargetKey]; 49 | if (DEVELOPMENT_TEAM) { 50 | runnerObj.DevelopmentTeam = DEVELOPMENT_TEAM; 51 | } 52 | 53 | fs.writeFileSync(projectPath, myProj.writeSync()); 54 | 55 | if (DEVELOPMENT_TEAM) { 56 | console.log('Successfully updated Bundle Id and Team Id.'); 57 | } else { 58 | console.log(`Successfully updated Bundle Id, but no Team Id was provided. Please update your team id manually in ${projectPath}, or reinstall the module with DEVELOPMENT_TEAM_ID in environment variable.`); 59 | } 60 | process.exit(0); 61 | } catch (e) { 62 | console.log('Failed to update Bundle Id and Team Id: ', e); 63 | } 64 | }; 65 | 66 | let version = doctorIOS.getXcodeVersion(); 67 | console.log(`Xcode version: ${version}`); 68 | let pkgName = ''; 69 | 70 | if (parseFloat(version) >= parseFloat('13')) { // 13 71 | // https://swift.org/blog/swift-5-5-released/ 72 | // Swift 5.5 is included in Xcode 13 73 | version = '13'; 74 | pkgName = `${xctestwdFrameworksPrefix}-${version}`; 75 | } else if (parseFloat(version) >= parseFloat('12.5')) { // 12 76 | // https://swift.org/blog/swift-5-4-released/ 77 | // Swift 5.4 is included in Xcode 12.5 78 | version = '12dot5'; 79 | pkgName = `${xctestwdFrameworksPrefix}-${version}`; 80 | } else if (parseFloat(version) >= parseFloat('12')) { // 12 81 | // https://swift.org/blog/swift-5-3-released/ 82 | // Swift 5.3 is also included in Xcode 12 83 | version = '12'; 84 | pkgName = `${xctestwdFrameworksPrefix}-${version}`; 85 | } else if (parseFloat(version) >= parseFloat('11.4')) { // 11.4 86 | // https://swift.org/blog/swift-5-2-released/ 87 | // Swift 5.2 ships as part of Xcode 11.4 88 | version = ''; 89 | // for Xcode 11.4 and prior, we use package without prefix for latest version 90 | // from Xcode 12 onwards, use package with prefix for latest version 91 | pkgName = xctestwdFrameworksPrefix; 92 | } else if (parseFloat(version) >= parseFloat('11.2')) { // 11.2 11.3 93 | version = 'iidotiidoti'; 94 | pkgName = `${xctestwdFrameworksPrefix}-${version}`; 95 | } else if (parseFloat(version) >= parseFloat('11.1')) { // 11.1 96 | version = '11dot1'; 97 | pkgName = `${xctestwdFrameworksPrefix}-${version}`; 98 | } else { 99 | console.log(_.chalk.red(`Xcode ${version} unsupported, please upgrade your xcode.`)); 100 | return; 101 | } 102 | console.log(`xctestwd frameworks package name: ${pkgName}`); 103 | 104 | let dir; 105 | 106 | try { 107 | dir = require.resolve(pkgName); 108 | } catch (e) { 109 | } 110 | 111 | if (!dir) { 112 | console.log(_.chalk.red(`can not find ${pkgName}, please check it.`)); 113 | return; 114 | } 115 | 116 | const originDir = path.join(dir, '..', 'Carthage'); 117 | const distDir = path.join(__dirname, '..'); 118 | console.log(`start to mv ${_.chalk.gray(originDir)} ${_.chalk.gray(distDir)}`); 119 | 120 | try { 121 | shelljs.mv('-n', originDir, distDir); 122 | } catch (e) { 123 | console.log(e); 124 | } 125 | 126 | const latestDir = path.join(distDir, 'Carthage'); 127 | 128 | if (_.isExistedDir(latestDir)) { 129 | console.log(_.chalk.cyan(`Carthage is existed: ${latestDir}`)); 130 | } else { 131 | throw _.chalk.red('Carthage is not existed, please reinstall!'); 132 | } 133 | 134 | if (/^\s*(iPhone .+?) \(.{8}-.{4}-.{4}-.{4}-.{12}\)/m.test(shelljs.exec('xcrun simctl list devices', { silent: true }).grep('iPhone').head({ '-n': 1 }).stdout)) { 135 | const name = RegExp.$1; 136 | 137 | // execute build of xctestrun file: 138 | shelljs.echo('preparing xctestrun build'); 139 | shelljs.exec('xcodebuild build -project "XCTestWD/XCTestWD.xcodeproj" -scheme "XCTestWDUITests" -destination "platform=iOS Simulator,name=' + name + '" -derivedDataPath "XCTestWD/build" -UseModernBuildSystem=NO'); 140 | 141 | // fetch out potential 142 | let result = fs.readdirSync(path.join(__dirname, '..', 'XCTestWD', 'build', 'Build', 'Products')).filter(fn => fn.match('.*simulator.*\.xctestrun')).shift(); 143 | console.log(`simulator optimization .xctestrun file generated: ${result}`); 144 | 145 | updateInformation(); 146 | } else { 147 | shelljs.echo('xcrun simctl list devices'); 148 | shelljs.exec('xcrun simctl list devices'); 149 | throw _.chalk.red('Failed to find iOS Simulator!'); 150 | } 151 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/XCTestWDServer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestWebDriverServer.swift 3 | // XCTestWebdriver 4 | // 5 | // Created by zhaoy on 21/4/17. 6 | // Copyright © 2017 XCTestWebdriver. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Swifter 11 | import CocoaLumberjackSwift 12 | 13 | struct XCTestWDDebugInfo { 14 | static let DebugLogPrefix = "XCTestWD-Debug-Info: \n" 15 | } 16 | 17 | public class XCTestWDServer { 18 | 19 | private let server = HttpServer() 20 | 21 | public init() { 22 | NSLog("initializing wd server") 23 | NSLog("check log dir @:\( NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true) )") 24 | setupLog() 25 | } 26 | 27 | public func startServer() { 28 | do { 29 | try server.start(fetchPort()) 30 | registerRouters() 31 | 32 | NSLog("\(Bundle.main.bundleIdentifier!)") 33 | NSLog("XCTestWDSetup->http://localhost:\(try! server.port())<-XCTestWDSetup") 34 | 35 | RunLoop.main.run() 36 | } catch { 37 | NSLog("Server start error: \(error)") 38 | } 39 | } 40 | 41 | public func stopServer() { 42 | server.stop() 43 | } 44 | 45 | private func setupLog() { 46 | if DDTTYLogger.sharedInstance != nil { 47 | DDLog.add(DDTTYLogger.sharedInstance!) 48 | } 49 | DDLog.add(DDASLLogger.sharedInstance) 50 | 51 | let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true) 52 | let logDir = "\(paths[0])/Logs" 53 | let ddLogFileManager : DDLogFileManagerDefault = DDLogFileManagerDefault.init(logsDirectory: logDir) 54 | let fileLogger: DDFileLogger = DDFileLogger.init(logFileManager: ddLogFileManager) 55 | fileLogger.rollingFrequency = TimeInterval(60*60*24) 56 | fileLogger.logFileManager.maximumNumberOfLogFiles = 7 57 | DDLog.add(fileLogger) 58 | DDLogError("\(XCTestWDDebugInfo.DebugLogPrefix)setup debug log") 59 | } 60 | 61 | private func registerRouters() { 62 | 63 | var controllers = [Controller]() 64 | 65 | controllers.append(XCTestWDAlertController()) 66 | controllers.append(XCTestWDElementController()) 67 | controllers.append(XCTestWDScreenshotController()) 68 | controllers.append(XCTestWDSessionController()) 69 | controllers.append(XCTestWDSourceController()) 70 | controllers.append(XCTestWDTitleController()) 71 | controllers.append(XCTestWDElementController()) 72 | controllers.append(XCTestWDWindowController()) 73 | controllers.append(XCTestWDUrlController()) 74 | 75 | for controller in controllers { 76 | let routes = Swift.type(of: controller).routes() 77 | for i in 0...routes.count - 1 { 78 | let (router, requestHandler) = routes[i] 79 | var routeMethod: HttpServer.MethodRoute? 80 | switch router.verb { 81 | case "post","POST": 82 | routeMethod = server.POST 83 | break 84 | case "get","GET": 85 | routeMethod = server.GET 86 | break 87 | case "put", "PUT": 88 | routeMethod = server.PUT 89 | break 90 | case "delete", "DELETE": 91 | routeMethod = server.DELETE 92 | break 93 | default: 94 | routeMethod = nil 95 | break 96 | } 97 | 98 | routeMethod?[router.path] = RouteOnMain(requestHandler) 99 | } 100 | } 101 | } 102 | 103 | private func fetchPort() -> in_port_t { 104 | 105 | let arguments = ProcessInfo.processInfo.arguments 106 | let index = arguments.index(of: "--port") 107 | var startingPort:Int = Int(portNumber()) 108 | if index != nil { 109 | if index! != NSNotFound || index! < arguments.count - 1{ 110 | startingPort = Int(arguments[index!+1])! 111 | } 112 | } 113 | 114 | var (isValid, _) = checkTcpPortForListen(port: in_port_t(startingPort)) 115 | while isValid == false { 116 | startingPort = startingPort + 1 117 | (isValid, _) = checkTcpPortForListen(port: in_port_t(startingPort)) 118 | } 119 | 120 | return in_port_t(startingPort) 121 | } 122 | 123 | //MARK: Check Port is occupied 124 | func checkTcpPortForListen(port: in_port_t) -> (Bool, descr: String){ 125 | 126 | let socketFileDescriptor = socket(AF_INET, SOCK_STREAM, 0) 127 | if socketFileDescriptor == -1 { 128 | return (false, "SocketCreationFailed, \(descriptionOfLastError())") 129 | } 130 | 131 | var addr = sockaddr_in() 132 | addr.sin_len = __uint8_t(MemoryLayout.size) 133 | addr.sin_family = sa_family_t(AF_INET) 134 | addr.sin_port = Int(OSHostByteOrder()) == OSLittleEndian ? _OSSwapInt16(port) : port 135 | addr.sin_addr = in_addr(s_addr: inet_addr("0.0.0.0")) 136 | addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0) 137 | var bind_addr = sockaddr() 138 | memcpy(&bind_addr, &addr, Int(MemoryLayout.size)) 139 | 140 | if bind(socketFileDescriptor, &bind_addr, socklen_t(MemoryLayout.size)) == -1 { 141 | let details = descriptionOfLastError() 142 | release(socket: socketFileDescriptor) 143 | return (false, "\(port), BindFailed, \(details)") 144 | } 145 | if listen(socketFileDescriptor, SOMAXCONN ) == -1 { 146 | let details = descriptionOfLastError() 147 | release(socket: socketFileDescriptor) 148 | return (false, "\(port), ListenFailed, \(details)") 149 | } 150 | release(socket: socketFileDescriptor) 151 | return (true, "\(port) is free for use") 152 | } 153 | 154 | func release(socket: Int32) { 155 | _ = Darwin.shutdown(socket, SHUT_RDWR) 156 | close(socket) 157 | } 158 | 159 | func descriptionOfLastError() -> String { 160 | return "Error: \(errno)" 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XCTestWD 2 | 3 | --- 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![node version][node-image]][node-url] 7 | [![npm download][download-image]][download-url] 8 | [![CircleCI](https://circleci.com/gh/macacajs/XCTestWD.svg?style=svg)](https://circleci.com/gh/macacajs/XCTestWD) 9 | 10 | [npm-image]: https://img.shields.io/npm/v/xctestwd.svg 11 | [npm-url]: https://npmjs.org/package/xctestwd 12 | [node-image]: https://img.shields.io/badge/node.js-%3E=_8-green.svg 13 | [node-url]: http://nodejs.org/download/ 14 | [download-image]: https://img.shields.io/npm/dm/xctestwd.svg 15 | [download-url]: https://npmjs.org/package/xctestwd 16 | 17 | > Swift implementation of WebDriver server for iOS that runs on Simulator/iOS devices. 18 | 19 | 20 | 21 | ## Contributors 22 | 23 | |[
SamuelZhaoY](https://github.com/SamuelZhaoY)
|[
xudafeng](https://github.com/xudafeng)
|[
paradite](https://github.com/paradite)
|[
holy-lousie](https://github.com/holy-lousie)
|[
adudurant](https://github.com/adudurant)
|[
Chan-Chun](https://github.com/Chan-Chun)
| 24 | | :---: | :---: | :---: | :---: | :---: | :---: | 25 | [
gurisxie](https://github.com/gurisxie)
|[
xqin](https://github.com/xqin)
|[
butterflyingdog](https://github.com/butterflyingdog)
|[
donlinglok](https://github.com/donlinglok)
|[
Nicolasyy](https://github.com/Nicolasyy)
|[
fengguochao](https://github.com/fengguochao)
26 | 27 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed Apr 15 2020 13:12:56 GMT+0800`. 28 | 29 | 30 | 31 | ## 1. Requirements 32 | 33 | - XCode version > 10.1 34 | - iOS version 11.0 and above. (there is significant change on XCUITest interfaces and system private headers, hence we decide to support newest OS version only) 35 | 36 | ### 1.1. Carthage 37 | 38 | Using Carthage with Xcode 12 39 | 40 | > https://github.com/Carthage/Carthage/blob/master/Documentation/Xcode12Workaround.md 41 | 42 | carthage.sh can be found at the root of the project 43 | 44 | ```bash 45 | ./carthage.sh bootstrap --platform iOS --cache-builds 46 | ``` 47 | 48 | ## 2. Starting XCTestWD 49 | 50 | XCTestWD can be either started with XCode IDE or via simple xcodebuild command line. By default, the webdriver agent occupies port `8001`. You can override the default port in XCode by searching `XCTESTWD_PORT` under project build settings. Alternatively, it can also be overrided when you execute command line method as specified in `2.2. Using Xcodebuild` 51 | 52 | ### 2.1. Using Xcode 53 | 54 | Download the project and open the XCode project, checkout the scheme `XCTestWDUITests` and run the test case `XCTextWDRunner` 55 | 56 | ### 2.2. Using XcodeBuild 57 | 58 | Open the terminal, go to the directory where contains `XCTestWD.xcodeproj` file and execute the following command: 59 | 60 | ```bash 61 | # 62 | # Change the port number to override the default port 63 | # 64 | xcodebuild -project XCTestWD.xcodeproj \ 65 | -scheme XCTestWDUITests \ 66 | -destination 'platform=iOS Simulator,name=iPhone 6' \ 67 | XCTESTWD_PORT=8001 \ 68 | clean test 69 | ``` 70 | 71 | To execute for iOS device, run the following command: 72 | 73 | ```bash 74 | # 75 | # Change the port number to override the default port 76 | # Specify the device name 77 | # 78 | xcodebuild -project XCTestWD.xcodeproj \ 79 | -scheme XCTestWDUITests \ 80 | -destination 'platform=iOS,name=(your device name)' \ 81 | XCTESTWD_PORT=8001 \ 82 | clean test 83 | ``` 84 | **Note:** For versions above wxtestwd 2.0.0, please install ideviceinstaller for supporting real device testing 85 | 86 | 87 | ## 3. Element Types 88 | 89 | In the current protocol, element strings for each `XCUIElementType` are generated based on the existing mapping in [reference/xctest/xcuielementtype](https://developer.apple.com/reference/xctest/xcuielementtype) 90 | 91 | 92 | ## 4. Common Issues 93 | 94 | ### 4.1 Socket hangup error 95 | 96 | Socket Hangup Error happens in the following two scenarios:
97 | - **Case 1**
98 | Issue:
99 | When you have some existing XCTestWD instances running and creating new ones.
100 | Solution:
101 | verify whether ideviceinstaller and xcrun is properly working on your device and simulator.
102 | Hint:
103 | https://github.com/libimobiledevice/ideviceinstaller/issues/48 104 | 105 | - **Case 2**
106 | Issue:
107 | When you have started the XCTestWD instance properly but fails in middle of a testing process.
108 | Solution:
109 | See the Macaca Service log to checkout which command leads the error. With detailed and comprehensive log information, please submit an issue to us.
110 | Optional:
111 | If you cannot get anything from macaca server log, open the XCTestWD in your node installation path and attatch for debugging on process 'XCTRunnerUITests-Runner'.
112 | 113 | **Additional Info**
114 | `The project path is at`

115 | ``` 116 | cd "$(npm root -g)/macaca-ios/node_modules/xctestwd" 117 | ``` 118 | 119 | `Set up the linebreak for swift error and exceptions:`

120 | 2017-12-14 10 56 33 121 | 122 | `Run your command regularly, once the driver has been initialized, attach the process:`

123 | 2017-12-14 10 55 14 124 | 125 | ### 4.2 Swift modules fails to compile 126 | 127 | Check carthage installation 128 | 129 | ### 4.3 Debug info 130 | 131 | Now XCTestWD supports gathering debug log into log files which is stored in "Your-App-Sandbox-Root"/Documents/Logs dir. For real devices, you can connect to itunes and choose backup for `XCTestWDUITests` and get the debug log. For iOS simulators, the log file is in your computer's simulator app directory like: 132 | 133 | ``` 134 | "/Users/${user-name}/Library/Developer/CoreSimulator/Devices \ 135 | /${device-id}/data/Containers/Data/Application/${app-id}/Documents/Logs" 136 | ``` 137 | 138 | You can use `xcrun simctl list` to get the id of the booted device. 139 | 140 | ### 4.4 141 | 142 | user PATH variable MACACA_XCTESTWD_ROOT_PATH to override the default one. 143 | 144 | ```bash 145 | MACACA_XCTESTWD_ROOT_PATH=/path/to/macaca_xctest app-inspector -u xxx --verbose 146 | ``` 147 | 148 | ### 4.5 149 | 150 | We update dependencies by using other npm packages, because the XCode upgrade swift syntax is often not backward compatible. 151 | 152 | | package | XCode version | notes | 153 | | --- | --- | --- | 154 | | [xctestwd-frameworks](https://github.com/macacajs/xctestwd-frameworks) | 11.2.1 | | 155 | -------------------------------------------------------------------------------- /lib/xctest-client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const url = require('url'); 5 | const path = require('path'); 6 | const iOSUtils = require('ios-utils'); 7 | const EventEmitter = require('events'); 8 | const childProcess = require('child_process'); 9 | 10 | const _ = require('./helper'); 11 | const pkg = require('../package'); 12 | const XCProxy = require('./proxy'); 13 | const logger = require('./logger'); 14 | const XCTestWD = require('./xctestwd'); 15 | 16 | const { 17 | detectPort 18 | } = _; 19 | const TEST_URL = pkg.site; 20 | const projectPath = XCTestWD.projectPath; 21 | const SERVER_URL_REG = XCTestWD.SERVER_URL_REG; 22 | const simulatorLogFlag = XCTestWD.simulatorLogFlag; 23 | 24 | class XCTest extends EventEmitter { 25 | constructor(options) { 26 | super(); 27 | this.proxy = null; 28 | this.capabilities = null; 29 | this.sessionId = null; 30 | this.device = null; 31 | this.deviceLogProc = null; 32 | this.runnerProc = null; 33 | this.iproxyProc = null; 34 | Object.assign(this, { 35 | proxyHost: '127.0.0.1', 36 | proxyPort: 8001, 37 | urlBase: 'wd/hub' 38 | }, options || {}); 39 | this.init(); 40 | } 41 | 42 | init() { 43 | this.checkProjectPath(); 44 | process.on('uncaughtException', (e) => { 45 | logger.error(`Uncaught Exception: ${e.stack}`); 46 | this.stop(); 47 | process.exit(1); 48 | }); 49 | process.on('exit', () => { 50 | this.stop(); 51 | }); 52 | } 53 | 54 | checkProjectPath() { 55 | if (_.isExistedDir(projectPath)) { 56 | logger.debug(`project path: ${projectPath}`); 57 | } else { 58 | logger.error('project path not found'); 59 | } 60 | } 61 | 62 | configUrl(str) { 63 | const urlObj = url.parse(str); 64 | this.proxyHost = urlObj.hostname; 65 | this.proxyPort = urlObj.port; 66 | } 67 | 68 | initProxy() { 69 | this.proxy = new XCProxy({ 70 | proxyHost: this.proxyHost, 71 | proxyPort: this.proxyPort, 72 | urlBase: this.urlBase 73 | }); 74 | } 75 | 76 | * startSimLog() { 77 | const logPath = yield this.startBootstrap(false); 78 | 79 | if (this.proxyHost && this.proxyPort) { 80 | return; 81 | } 82 | 83 | const logDir = path.resolve(logPath); 84 | 85 | return _.retry(() => { 86 | return new Promise((resolve, reject) => { 87 | let logTxtFile = path.join(logDir, '..', 'StandardOutputAndStandardError.txt'); 88 | logTxtFile = logDir.replace(/(\s)/, '\\ '); 89 | 90 | logger.info(`Read simulator log at: ${logTxtFile}`); 91 | 92 | if (!_.isExistedFile(logTxtFile)) { 93 | return reject(); 94 | } 95 | let args = `-f -n 0 ${logTxtFile}`.split(' '); 96 | var proc = childProcess.spawn('tail', args, {}); 97 | this.deviceLogProc = proc; 98 | 99 | proc.stderr.setEncoding('utf8'); 100 | proc.stdout.setEncoding('utf8'); 101 | 102 | proc.stdout.on('data', data => { 103 | 104 | // avoid logout long data such as bitmap 105 | if (data.length <= 300 && logger.debugMode) { 106 | logger.debug(data); 107 | } 108 | 109 | let match = SERVER_URL_REG.exec(data); 110 | if (match) { 111 | const url = match[1]; 112 | if (url.startsWith('http://')) { 113 | this.configUrl(url); 114 | resolve(); 115 | } 116 | } 117 | }); 118 | 119 | proc.stderr.on('data', data => { 120 | logger.debug(data); 121 | }); 122 | 123 | proc.stdout.on('error', (err) => { 124 | logger.warn(`simulator log process error with ${err}`); 125 | }); 126 | 127 | proc.on('exit', (code, signal) => { 128 | logger.warn(`simulator log process exit with code: ${code}, signal: ${signal}`); 129 | reject(); 130 | }); 131 | }); 132 | }, 1000, Infinity); 133 | } 134 | 135 | * startDeviceLog() { 136 | yield this.startBootstrap(true); 137 | 138 | if (this.proxyHost && this.proxyPort) { 139 | return; 140 | } 141 | 142 | var proc = childProcess.spawn(iOSUtils.devicelog.binPath, [this.device.deviceId], {}); 143 | this.deviceLogProc = proc; 144 | 145 | proc.stderr.setEncoding('utf8'); 146 | proc.stdout.setEncoding('utf8'); 147 | 148 | return new Promise((resolve, reject) => { 149 | proc.stdout.on('data', data => { 150 | 151 | // avoid logout long data such as bitmap 152 | if (data.length <= 300 && logger.debugMode) { 153 | logger.debug(data); 154 | } 155 | 156 | let match = SERVER_URL_REG.exec(data); 157 | if (match) { 158 | const url = match[1]; 159 | if (url.startsWith('http://')) { 160 | this.configUrl(url); 161 | resolve(); 162 | } 163 | } 164 | }); 165 | 166 | proc.stderr.on('data', data => { 167 | logger.debug(data); 168 | }); 169 | 170 | proc.stdout.on('error', (err) => { 171 | logger.warn(`devicelog error with ${err}`); 172 | }); 173 | 174 | proc.on('exit', (code, signal) => { 175 | logger.warn(`devicelog exit with code: ${code}, signal: ${signal}`); 176 | reject(); 177 | }); 178 | }); 179 | } 180 | 181 | * startBootstrap(isDevice) { 182 | return new Promise((resolve, reject) => { 183 | logger.info(`XCTestWD version: ${XCTestWD.version}`); 184 | 185 | var args = `clean test -project ${XCTestWD.projectPath} -scheme XCTestWDUITests -destination id=${this.device.deviceId} -UseModernBuildSystem=NO XCTESTWD_PORT=${this.proxyPort}`.split(' '); 186 | 187 | // check potential optimization provided by .xctestrun file, which allows xctestwd can be executed concurrently on multiple devices 188 | let xctestrun_path = path.join(__dirname, '..', 'XCTestWD', 'build', 'Build', 'Products'); 189 | let xctestrun = fs.readdirSync(xctestrun_path).filter(fn => fn.match(isDevice ? '.*device.*\.xctestrun' : '.*simulator.*\.xctestrun')).shift(); 190 | if (xctestrun) { 191 | args = `test-without-building -xctestrun ${xctestrun_path}/${xctestrun} -destination id=${this.device.deviceId} XCTESTWD_PORT=${this.proxyPort}`.split(' '); 192 | } 193 | 194 | var env = _.merge({}, process.env, { 195 | XCTESTWD_PORT: this.proxyPort 196 | }); 197 | 198 | var proc = childProcess.spawn('xcodebuild', args, { 199 | env: env 200 | }); 201 | this.runnerProc = proc; 202 | proc.stderr.setEncoding('utf8'); 203 | proc.stdout.setEncoding('utf8'); 204 | 205 | proc.stdout.on('data', data => { 206 | logger.debug(data); 207 | if (xctestrun) { 208 | let match = SERVER_URL_REG.exec(data); 209 | if (match) { 210 | const url = match[1]; 211 | if (url.startsWith('http://')) { 212 | logger.debug('hitted for xctestrun mode'); 213 | this.configUrl(url); 214 | resolve(); 215 | } 216 | } 217 | } else { 218 | let match = SERVER_URL_REG.exec(data); 219 | if (match) { 220 | const url = match[1]; 221 | if (url.startsWith('http://')) { 222 | logger.debug('hit xcode 13 mode'); 223 | this.configUrl(url); 224 | resolve(); 225 | } 226 | } 227 | } 228 | }); 229 | 230 | proc.stderr.on('data', data => { 231 | if (data.length > 1000) { 232 | logger.warn(data.slice(0, 1000) + '...'); 233 | } else { 234 | logger.warn(data); 235 | } 236 | if (!xctestrun) { 237 | if (~data.indexOf(simulatorLogFlag)) { 238 | const list = data.split(simulatorLogFlag); 239 | const res = list[1].trim(); 240 | logger.debug('hitted for default mode'); 241 | resolve(res); 242 | } else { 243 | // https://github.com/macacajs/XCTestWD/issues/191 244 | resolve(''); 245 | // logger.debug(`please check project: ${projectPath}`); 246 | } 247 | } 248 | }); 249 | 250 | proc.stdout.on('error', (err) => { 251 | logger.warn(`xctest client error with ${err}`); 252 | logger.debug(`please check project: ${projectPath}`); 253 | }); 254 | 255 | proc.on('exit', (code, signal) => { 256 | this.stop(); 257 | logger.warn(`xctest client exit with code: ${code}, signal: ${signal}`); 258 | }); 259 | 260 | }); 261 | } 262 | 263 | * startIproxy() { 264 | let args = [`${this.proxyPort}:${this.proxyPort}`, '-u', this.device.deviceId]; 265 | const IOS_USBMUXD_IPROXY = 'iproxy'; 266 | const binPath = yield _.exec(`which ${IOS_USBMUXD_IPROXY}`); 267 | 268 | var proc = childProcess.spawn(binPath, args); 269 | 270 | this.iproxyProc = proc; 271 | proc.stderr.setEncoding('utf8'); 272 | proc.stdout.setEncoding('utf8'); 273 | 274 | proc.stdout.on('data', () => { 275 | }); 276 | 277 | proc.stderr.on('data', (data) => { 278 | if (data.length > 1000) { 279 | logger.warn(data.slice(0, 1000) + '...'); 280 | } else { 281 | logger.warn(data); 282 | } 283 | }); 284 | 285 | proc.stdout.on('error', (err) => { 286 | logger.warn(`${IOS_USBMUXD_IPROXY} error with ${err}`); 287 | }); 288 | 289 | proc.on('exit', (code, signal) => { 290 | logger.warn(`${IOS_USBMUXD_IPROXY} exit with code: ${code}, signal: ${signal}`); 291 | }); 292 | } 293 | 294 | * start(caps) { 295 | try { 296 | this.proxyPort = yield detectPort(this.proxyPort); 297 | 298 | this.capabilities = caps; 299 | const xcodeVersion = yield iOSUtils.getXcodeVersion(); 300 | 301 | logger.debug(`xcode version: ${xcodeVersion}`); 302 | 303 | var deviceInfo = iOSUtils.getDeviceInfo(this.device.deviceId); 304 | 305 | if (deviceInfo.isRealIOS) { 306 | yield this.startDeviceLog(); 307 | yield this.startIproxy(); 308 | } else { 309 | yield this.startSimLog(); 310 | } 311 | 312 | logger.info(`${pkg.name} start with port: ${this.proxyPort}`); 313 | 314 | this.initProxy(); 315 | 316 | if (caps.desiredCapabilities.browserName === 'Safari') { 317 | var promise = this.proxy.send(`/${this.urlBase}/session`, 'POST', { 318 | desiredCapabilities: { 319 | bundleId: 'com.apple.mobilesafari' 320 | } 321 | }); 322 | return yield Promise.all([this.device.openURL(TEST_URL), promise]); 323 | } else { 324 | return yield this.proxy.send(`/${this.urlBase}/session`, 'POST', caps); 325 | } 326 | } catch (err) { 327 | logger.debug(`Fail to start xctest: ${err}`); 328 | this.stop(); 329 | throw err; 330 | } 331 | } 332 | 333 | stop() { 334 | if (this.deviceLogProc) { 335 | logger.debug(`killing deviceLogProc pid: ${this.deviceLogProc.pid}`); 336 | this.deviceLogProc.kill('SIGKILL'); 337 | this.deviceLogProc = null; 338 | } 339 | if (this.runnerProc) { 340 | logger.debug(`killing runnerProc pid: ${this.runnerProc.pid}`); 341 | this.runnerProc.kill('SIGKILL'); 342 | this.runnerProc = null; 343 | } 344 | 345 | if (this.iproxyProc) { 346 | logger.debug(`killing iproxyProc pid: ${this.iproxyProc.pid}`); 347 | this.iproxyProc.kill('SIGKILL'); 348 | this.iproxyProc = null; 349 | } 350 | } 351 | 352 | sendCommand(url, method, body) { 353 | return this.proxy.send(url, method, body); 354 | } 355 | } 356 | 357 | module.exports = XCTest; 358 | module.exports.XCTestWD = XCTestWD; 359 | -------------------------------------------------------------------------------- /XCTestWD/XCTestWD/Server/Modules/Utils/XCTestWDAccessibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCUIElement+XCTestWDAccessibility.swift 3 | // XCTestWD 4 | // 5 | // Created by zhaoy on 29/4/17. 6 | // Copyright © 2017 XCTestWD. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | func firstNonEmptyValue(_ value1:String?, _ value2:String?) -> String? { 13 | if value1 != nil && (value1?.count)! > 0 { 14 | return value1 15 | } else { 16 | return value2 17 | } 18 | } 19 | 20 | extension XCUIElement { 21 | 22 | func wdValue() -> Any! { 23 | var value = self.value 24 | if self.elementType == XCUIElement.ElementType.staticText { 25 | if self.value != nil { 26 | value = self.value 27 | } else { 28 | value = self.label 29 | } 30 | } 31 | if self.elementType == XCUIElement.ElementType.button { 32 | if let temp = self.value { 33 | if ((temp as? String)?.count) ?? 0 > 0 { 34 | value = self.value 35 | } else { 36 | value = self.isSelected 37 | } 38 | } else { 39 | value = self.isSelected 40 | } 41 | } 42 | if self.elementType == XCUIElement.ElementType.switch { 43 | value = (self.value as! NSString).doubleValue > 0 44 | } 45 | if self.elementType == XCUIElement.ElementType.textField || 46 | self.elementType == XCUIElement.ElementType.textView || 47 | self.elementType == XCUIElement.ElementType.secureTextField { 48 | if let temp = self.value { 49 | if let str = temp as? String { 50 | if str.count > 0 { 51 | value = self.value 52 | } else { 53 | value = self.placeholderValue 54 | } 55 | } else { 56 | value = self.value 57 | } 58 | } else { 59 | value = self.placeholderValue 60 | } 61 | } 62 | 63 | return value 64 | } 65 | 66 | 67 | func wdLabel() -> String { 68 | if self.elementType == XCUIElement.ElementType.textField { 69 | return self.label 70 | } else if self.label.count > 0 { 71 | return self.label 72 | } else { 73 | return "" 74 | } 75 | } 76 | 77 | func wdName() -> String? { 78 | let name = (firstNonEmptyValue(self.identifier, self.label)) 79 | if name?.count == 0 { 80 | return nil 81 | } else { 82 | return name 83 | } 84 | } 85 | 86 | 87 | func wdType() -> String { 88 | return XCUIElementTypeTransformer.singleton.stringWithElementType(self.elementType) 89 | } 90 | 91 | func isWDEnabled() -> Bool { 92 | return self.isEnabled 93 | } 94 | 95 | func wdFrame() -> CGRect { 96 | return self.frame.integral 97 | } 98 | 99 | func wdRect() -> [String:CGFloat] { 100 | return [ 101 | "x":self.frame.minX, 102 | "y":self.frame.minY, 103 | "width":self.frame.width, 104 | "height":self.frame.height] 105 | } 106 | 107 | func checkLastSnapShot() -> XCElementSnapshot { 108 | self.fb_nativeResolve() 109 | return self.fb_lastSnapshot() 110 | } 111 | 112 | //MARK: element query 113 | 114 | func descendantsMatchingXPathQuery(xpathQuery:String, returnAfterFirstMatch:Bool) -> [XCUIElement]? { 115 | 116 | let query = xpathQuery.replacingOccurrences(of: "XCUIElementTypeAny", with: "*") 117 | var matchSnapShots = XCTestWDXPath.findMatchesIn(self.fb_lastSnapshot(), query) 118 | 119 | if matchSnapShots == nil || matchSnapShots!.count == 0 { 120 | return [XCUIElement]() 121 | } 122 | 123 | if returnAfterFirstMatch { 124 | matchSnapShots = [matchSnapShots!.first!] 125 | } 126 | 127 | var matchingTypes = Set() 128 | for snapshot in matchSnapShots! { 129 | matchingTypes.insert(XCUIElementTypeTransformer.singleton.elementTypeWithTypeName(snapshot.wdType())) 130 | } 131 | 132 | var map = [XCUIElement.ElementType:[XCUIElement]]() 133 | for type in matchingTypes { 134 | let descendantsOfType = self.descendants(matching: type).allElementsBoundByIndex 135 | map[type] = descendantsOfType 136 | } 137 | 138 | var matchingElements = [XCUIElement]() 139 | for snapshot in matchSnapShots! { 140 | var elements = map[snapshot.elementType] 141 | if query.contains("last()") { 142 | elements = elements?.reversed() 143 | } 144 | 145 | innerLoop: for element in elements! { 146 | if element.checkLastSnapShot()._matchesElement(snapshot) { 147 | matchingElements.append(element) 148 | break innerLoop 149 | } 150 | } 151 | 152 | } 153 | 154 | return matchingElements 155 | } 156 | 157 | func descendantsMatchingIdentifier(accessibilityId:String, returnAfterFirstMatch:Bool) -> [XCUIElement]? { 158 | var result = [XCUIElement]() 159 | 160 | if self.identifier == accessibilityId { 161 | result.append(self) 162 | if returnAfterFirstMatch { 163 | return result 164 | } 165 | } 166 | 167 | let query = self.descendants(matching: XCUIElement.ElementType.any).matching(identifier: accessibilityId); 168 | result.append(contentsOf: XCUIElement.extractMatchElementFromQuery(query: query, returnAfterFirstMatch: returnAfterFirstMatch)) 169 | 170 | return result 171 | } 172 | 173 | func descendantsMatchingClassName(className:String, returnAfterFirstMatch:Bool) -> [XCUIElement]? { 174 | var result = [XCUIElement]() 175 | 176 | let type = XCUIElementTypeTransformer.singleton.elementTypeWithTypeName(className) 177 | if self.elementType == type || type == XCUIElement.ElementType.any { 178 | result.append(self); 179 | if returnAfterFirstMatch { 180 | return result 181 | } 182 | } 183 | 184 | let query = self.descendants(matching: type); 185 | result.append(contentsOf: XCUIElement.extractMatchElementFromQuery(query: query, returnAfterFirstMatch: returnAfterFirstMatch)) 186 | 187 | return result 188 | } 189 | 190 | func descendantsMatching(Predicate predicate:NSPredicate, _ returnFirstMatch:Bool) -> [XCUIElement] { 191 | let formattedPredicate = NSPredicate.xctestWDformatSearch(predicate) 192 | var result:[XCUIElement] = [] 193 | 194 | if formattedPredicate?.evaluate(with: self.fb_lastSnapshot) ?? false { 195 | if returnFirstMatch { 196 | return [self] 197 | } 198 | 199 | result.append(self) 200 | } 201 | 202 | let query = self.descendants(matching: XCUIElement.ElementType.any).matching(formattedPredicate!) 203 | 204 | if returnFirstMatch { 205 | result.append(query.element(boundBy: 0)) 206 | } else { 207 | result.append(contentsOf: query.allElementsBoundByIndex) 208 | } 209 | 210 | return result 211 | } 212 | 213 | static func extractMatchElementFromQuery(query:XCUIElementQuery, returnAfterFirstMatch:Bool) -> [XCUIElement] { 214 | if !returnAfterFirstMatch { 215 | return query.allElementsBoundByIndex 216 | } 217 | 218 | let matchedElement = query.element(boundBy: 0) 219 | 220 | if query.allElementsBoundByIndex.count == 0{ 221 | return [XCUIElement]() 222 | } else { 223 | return [matchedElement] 224 | } 225 | } 226 | 227 | open func customValue(forKey key: String) -> Any? { 228 | if key.lowercased().contains("enable") { 229 | return self.isEnabled 230 | } else if key.lowercased().contains("name") { 231 | return self.wdName() ?? "" 232 | } else if key.lowercased().contains("value") { 233 | return self.wdValue() 234 | } else if key.lowercased().contains("label") { 235 | return self.wdLabel() 236 | } else if key.lowercased().contains("type") { 237 | return self.wdType() 238 | } else if key.lowercased().contains("visible") { 239 | if self.fb_lastSnapshot() == nil { 240 | self.fb_nativeResolve() 241 | } 242 | return self.fb_lastSnapshot().isWDVisible() 243 | } else if key.lowercased().contains("access") { 244 | if self.fb_lastSnapshot() == nil { 245 | self.fb_nativeResolve() 246 | } 247 | return self.fb_lastSnapshot().isAccessibile() 248 | } 249 | 250 | return "" 251 | } 252 | 253 | open override func value(forUndefinedKey key: String) -> Any? { 254 | return "" 255 | } 256 | 257 | //MARK: Commands 258 | func tree() -> [String : AnyObject]? { 259 | 260 | self.fb_nativeResolve() 261 | 262 | return dictionaryForElement(self.fb_lastSnapshot()) 263 | } 264 | 265 | func digest(windowName : String) -> String { 266 | let description = "\(windowName)_\(self.buttons.count)_\(self.textViews.count)_\(self.textFields.count)_\(self.otherElements.count)_\(self.descendants(matching:.any).count)_\(self.traits())" 267 | 268 | return description 269 | } 270 | 271 | func accessibilityTree() -> [String : AnyObject]? { 272 | self.fb_nativeResolve() 273 | let _ = self.query 274 | 275 | return accessibilityInfoForElement(self.fb_lastSnapshot()) 276 | } 277 | 278 | //MARK: Private Methods 279 | func dictionaryForElement(_ snapshot:XCElementSnapshot) -> [String : AnyObject]? { 280 | var info = [String : AnyObject]() 281 | info["type"] = XCUIElementTypeTransformer.singleton.shortStringWithElementType(snapshot.elementType) as AnyObject? 282 | info["rawIndentifier"] = (snapshot.identifier.count > 0 ? snapshot.identifier : "") as AnyObject 283 | info["name"] = snapshot.wdName() as AnyObject? ?? "" as AnyObject 284 | info["value"] = snapshot.wdValue() as AnyObject? ?? "" as AnyObject 285 | info["label"] = snapshot.wdLabel() as AnyObject? ?? "" as AnyObject 286 | info["rect"] = snapshot.wdRect() as AnyObject 287 | info["frame"] = NSStringFromCGRect(snapshot.wdFrame()) as AnyObject 288 | info["isEnabled"] = snapshot.isWDEnabled() as AnyObject 289 | info["isVisible"] = snapshot.isWDVisible() as AnyObject 290 | 291 | // If block is not visible, return 292 | if info["isVisible"] as! Bool == false { 293 | return nil; 294 | } 295 | 296 | // If block is visible, iterate through all its children 297 | let childrenElements = snapshot.children 298 | 299 | if childrenElements != nil && childrenElements!.count > 0 { 300 | var children = [AnyObject]() 301 | for child in childrenElements! { 302 | if let temp = dictionaryForElement(child as! XCElementSnapshot) { 303 | children.append(temp as AnyObject) 304 | } 305 | } 306 | 307 | if children.count > 0 { 308 | info["children"] = children as AnyObject 309 | } 310 | } 311 | 312 | return info 313 | } 314 | 315 | func accessibilityInfoForElement(_ snapshot:XCElementSnapshot) -> [String:AnyObject]? { 316 | let isAccessible = snapshot.isWDAccessible() 317 | let isVisible = snapshot.isWDVisible() 318 | 319 | var info = [String: AnyObject]() 320 | 321 | if isAccessible { 322 | if isVisible { 323 | info["value"] = snapshot.wdValue as AnyObject 324 | info["label"] = snapshot.wdLabel as AnyObject 325 | } 326 | } 327 | else { 328 | var children = [AnyObject]() 329 | let childrenElements = snapshot.children 330 | for childSnapshot in childrenElements! { 331 | let childInfo: [String: AnyObject] = self.accessibilityInfoForElement(childSnapshot as! XCElementSnapshot)! 332 | if childInfo.keys.count > 0{ 333 | children.append(childInfo as AnyObject) 334 | } 335 | } 336 | 337 | if children.count > 0 { 338 | info["children"] = children as AnyObject 339 | } 340 | } 341 | 342 | return info 343 | } 344 | 345 | } 346 | 347 | extension XCElementSnapshot { 348 | 349 | func wdValue() -> Any? { 350 | var value = self.value 351 | if self.elementType == XCUIElement.ElementType.staticText { 352 | if self.value != nil { 353 | value = self.value 354 | } else { 355 | value = self.label 356 | } 357 | } 358 | if self.elementType == XCUIElement.ElementType.button { 359 | if let temp = self.value { 360 | if ((temp as? String)?.count) ?? 0 > 0 { 361 | value = self.value 362 | } else { 363 | value = self.isSelected 364 | } 365 | } else { 366 | value = self.isSelected 367 | } 368 | } 369 | if self.elementType == XCUIElement.ElementType.switch { 370 | value = (self.value as! NSString).doubleValue > 0 371 | } 372 | if self.elementType == XCUIElement.ElementType.textField || 373 | self.elementType == XCUIElement.ElementType.textView || 374 | self.elementType == XCUIElement.ElementType.secureTextField { 375 | if let temp = self.value { 376 | if let str = temp as? String { 377 | if str.count > 0 { 378 | value = self.value 379 | } else { 380 | value = self.placeholderValue 381 | } 382 | } else { 383 | value = self.value 384 | } 385 | } else { 386 | value = self.placeholderValue 387 | } 388 | } 389 | 390 | return value 391 | } 392 | 393 | func wdLabel() -> String? { 394 | if self.elementType == XCUIElement.ElementType.textField { 395 | return self.label 396 | } else if self.label.count > 0 { 397 | return self.label 398 | } else { 399 | return "" 400 | } 401 | } 402 | 403 | func wdName() -> String? { 404 | let name = (firstNonEmptyValue(self.identifier, self.label)) 405 | if name?.count == 0 { 406 | return "" 407 | } else { 408 | return name 409 | } 410 | } 411 | 412 | func wdType() -> String { 413 | return XCUIElementTypeTransformer.singleton.stringWithElementType(self.elementType) 414 | } 415 | 416 | func isWDEnabled() -> Bool { 417 | return self.isEnabled 418 | } 419 | 420 | func wdFrame() -> CGRect { 421 | return self.frame.integral 422 | } 423 | 424 | func wdRect() -> [String:CGFloat] { 425 | return [ 426 | "x":self.frame.minX, 427 | "y":self.frame.minY, 428 | "width":self.frame.width, 429 | "height":self.frame.height] 430 | } 431 | 432 | func isWDVisible() -> Bool { 433 | if self.frame.isEmpty || self.visibleFrame.isEmpty { 434 | return false 435 | } 436 | 437 | let app: XCElementSnapshot? = rootElement() as! XCElementSnapshot? 438 | let screenSize: CGSize? = MathUtils.adjustDimensionsForApplication((app?.frame.size)!, (XCUIDevice.shared.orientation)) 439 | let screenFrame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat((screenSize?.width)!), height: CGFloat((screenSize?.height)!)) 440 | 441 | if !visibleFrame.intersects(screenFrame) { 442 | return false; 443 | } 444 | 445 | return true 446 | } 447 | 448 | //MARK: Accessibility Measurement 449 | func isWDAccessible() -> Bool { 450 | if self.elementType == XCUIElement.ElementType.cell { 451 | if !isAccessibile() { 452 | let containerView: XCElementSnapshot? = children.first as? XCElementSnapshot 453 | if !(containerView?.isAccessibile())! { 454 | return false 455 | } 456 | } 457 | } 458 | else if self.elementType != XCUIElement.ElementType.textField && self.elementType != XCUIElement.ElementType.secureTextField { 459 | if !isAccessibile() { 460 | return false 461 | } 462 | } 463 | 464 | var parentSnapshot: XCElementSnapshot? = parent 465 | while (parentSnapshot != nil) { 466 | if ((parentSnapshot?.isAccessibile())! && parentSnapshot?.elementType != XCUIElement.ElementType.table) { 467 | return false; 468 | } 469 | 470 | parentSnapshot = parentSnapshot?.parent 471 | } 472 | 473 | return true 474 | } 475 | 476 | func isAccessibile() -> Bool { 477 | return self.attributeValue(XCAXAIsElementAttribute)?.boolValue ?? false 478 | } 479 | 480 | func attributeValue(_ number:NSNumber) -> AnyObject? { 481 | let attributesResult = (XCTestXCAXClientProxy.sharedClient() ).attributes(forElement: self, attributes: [number]) 482 | return attributesResult as AnyObject? 483 | } 484 | 485 | } 486 | 487 | --------------------------------------------------------------------------------