Skip to content

Commit

Permalink
Fix browser tools on macOS Catalina (closes #171) (#172)
Browse files Browse the repository at this point in the history
* Fix find-window for macOS Catalina

* Rebuild binary

* Rewrite open in objc

* Add single binary

* Try to fix

* Final commit

* Revert lint

* Fix open safari
  • Loading branch information
AndreyBelym authored and helen-dikareva committed Oct 15, 2019
1 parent 543c521 commit 9002d21
Show file tree
Hide file tree
Showing 51 changed files with 397 additions and 97 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@

/src/natives/*/any/bin/**
/src/natives/*/any/obj/**

/src/natives/*/mac/obj/**
26 changes: 16 additions & 10 deletions Gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ var chmod = require('gulp-chmod');
var del = require('del');
var through = require('through2');
var Promise = require('pinkie');
var assign = require('lodash').assign;
var platform = require('linux-platform-info').platform;
var tmp = require('tmp');
var tar = require('tar-stream');
Expand All @@ -26,6 +25,10 @@ var packageInfo = require('./package.json');
const EXEC_MASK = parseInt('111', 8);
const UNIX_BINARY_PATH_RE = /^package\/bin\/(mac|linux)/;

const MACOSX_DEPLOYMENT_TARGET = '10.14';
const MAC_APP_NAME = 'TestCafe Browser Tools.app';
const MAC_BINARY_PATH = `bin/mac/${MAC_APP_NAME}/Contents/MacOS`;

tmp.setGracefulCleanup();

function make (options) {
Expand All @@ -37,7 +40,7 @@ function make (options) {

var dirPath = path.dirname(file.path).replace(/ /g, '\\ ');

execa.shell('make -C ' + dirPath, { env: assign({}, process.env, options) })
execa.shell('make -C ' + dirPath, { env: { ...process.env, ...options } })
.then(function () {
callback(null, file);
})
Expand Down Expand Up @@ -81,22 +84,25 @@ gulp.task('copy-win-executables', ['build-win-executables'], function () {

// Mac bin
gulp.task('clean-mac-bin', function () {
return del('bin/mac');
return del(MAC_BINARY_PATH);
});

gulp.task('build-mac-executables', ['clean-mac-bin'], function () {
return gulp
.src('src/natives/**/@(mac|any)/Makefile')
.src('src/natives/!(app)/@(mac|any)/Makefile')
.pipe(make({
DEST: path.join(__dirname, 'bin/mac')
DEST: 'obj',
MACOSX_DEPLOYMENT_TARGET
}));
});

gulp.task('copy-mac-scripts', ['clean-mac-bin'], function () {
gulp.task('build-mac-app', ['build-mac-executables'], function () {
return gulp
.src('src/natives/**/mac/*.scpt')
.pipe(flatten())
.pipe(gulp.dest('bin/mac'));
.src('src/natives/app/mac/Makefile')
.pipe(make({
DEST: path.join(__dirname, MAC_BINARY_PATH),
MACOSX_DEPLOYMENT_TARGET
}));
});

// Linux bin
Expand Down Expand Up @@ -211,7 +217,7 @@ gulp.task('transpile-lib', ['lint', 'clean-lib'], function () {
gulp.task('build-lib', ['transpile-lib', 'docs']);

gulp.task('build-win', ['build-lib', 'copy-win-executables']);
gulp.task('build-mac', ['build-lib', 'build-mac-executables', 'copy-mac-scripts']);
gulp.task('build-mac', ['build-lib', 'build-mac-app']);
gulp.task('build-linux', ['build-lib', 'build-linux-executables', 'copy-linux-scripts']);

gulp.task('docs', ['transpile-lib'], function () {
Expand Down
38 changes: 38 additions & 0 deletions bin/mac/TestCafe Browser Tools.app/Contents/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>testcafe-browser-tools</string>
<key>CFBundleIdentifier</key>
<string>com.devexpress.testcafe-browser-tools</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>TestCafe Browser Tools</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleIconFile</key>
<string>testcafe.icns</string>
<key>LSUIElement</key>
<true/>
<key>NSAppleEventsUsageDescription</key>
<string>TestCafe uses Automation to take screenshots and control browser windows</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 DevExpress. All rights reserved.</string>
<key>NSSupportsAutomaticTermination</key>
<true/>
<key>NSSupportsSuddenTermination</key>
<true/>
</dict>
</plist>
Binary file not shown.
1 change: 1 addition & 0 deletions bin/mac/TestCafe Browser Tools.app/Contents/PkgInfo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
APPL????
Binary file not shown.
Binary file removed bin/mac/bring-to-front
Binary file not shown.
Binary file removed bin/mac/close
Binary file not shown.
Binary file removed bin/mac/find-window
Binary file not shown.
Binary file removed bin/mac/generate-thumbnail
Binary file not shown.
Binary file removed bin/mac/get-window-bounds
Binary file not shown.
Binary file removed bin/mac/get-window-max-bounds
Binary file not shown.
Binary file removed bin/mac/get-window-size
Binary file not shown.
Binary file removed bin/mac/open.scpt
Binary file not shown.
Binary file removed bin/mac/resize
Binary file not shown.
Binary file removed bin/mac/screenshot
Binary file not shown.
Binary file removed bin/mac/set-window-bounds
Binary file not shown.
Binary file added bin/mac/testcafe-browser-tools
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
"dependencies": {
"array-find": "^1.0.0",
"babel-runtime": "^5.6.15",
"del": "^5.1.0",
"graceful-fs": "^4.1.11",
"linux-platform-info": "^0.0.3",
"mkdirp": "^0.5.1",
"mustache": "^2.1.2",
"nanoid": "^2.1.3",
"os-family": "^1.0.0",
"pify": "^2.3.0",
"pinkie": "^2.0.1",
Expand All @@ -30,7 +32,6 @@
"babel-eslint": "^4.0.10",
"body-parser": "^1.13.2",
"chai": "^3.2.0",
"del": "^2.2.1",
"dmd-plugin-async": "^0.1.1",
"eslint": "4.18.2",
"eslint-plugin-babel": "^2.1.1",
Expand Down
4 changes: 2 additions & 2 deletions src/aliases.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ const ALIASES = {
'safari': {
nameRe: /safari/i,
cmd: '',
path: BINARIES.open,
macOpenCmdTemplate: '/usr/bin/osascript "{{{path}}}" {{{pageUrl}}} --args {{{cmd}}}'
path: BINARIES.app,
macOpenCmdTemplate: `open -n -a "{{{path}}}" --args /dev/null open {{{pageUrl}}} {{{cmd}}}`
},

'edge': {
Expand Down
31 changes: 26 additions & 5 deletions src/api/find-window.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import OS from 'os-family';
import { EOL } from 'os';
import delay from '../utils/delay';
import { execFile } from '../utils/exec';
import BINARIES from '../binaries';

Expand All @@ -12,12 +13,35 @@ import BINARIES from '../binaries';
* @param {string} pageTitle - The title of the web page opened in a window whose descriptor should be retrieved.
* @returns {object} a platform-specific window descriptor that can be used as a window identifier.
**/
const GRANT_PERMISSIONS_RETRY_COUNT = 10;
const GRANT_PERMISSIONS_DELAY = 3000;
const GRANT_PERMISSIONS_EXIT_CODE = 2;

async function runFindWindowBinary (pageTitle) {
for (let i = 0; i < GRANT_PERMISSIONS_RETRY_COUNT; i++) {
try {
return await execFile(BINARIES.findWindow, [pageTitle]);
}
catch (err) {
const code = err.status || err.code;

if (code === GRANT_PERMISSIONS_EXIT_CODE) {
await delay(GRANT_PERMISSIONS_DELAY);

continue;
}

throw err;
}
}
}

export default async function (pageTitle) {
var res = null;
var windowParams = [];

try {
res = await execFile(BINARIES.findWindow, [pageTitle]);
res = await runFindWindowBinary(pageTitle);
}
catch (err) {
return null;
Expand All @@ -28,11 +52,8 @@ export default async function (pageTitle) {
if (OS.win)
return { hwnd: windowParams[0], browser: windowParams[1] };

if (OS.mac) {
windowParams = windowParams.slice(windowParams.length - 4);

if (OS.mac)
return { processId: windowParams[0], cocoaId: windowParams[1], windowId: windowParams[2] };
}

if (OS.linux)
return { windowId: windowParams[0] };
Expand Down
27 changes: 16 additions & 11 deletions src/binaries.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { join } from 'path';
import OS from 'os-family';
import { toAbsPath } from 'read-file-relative';
import { platform } from 'linux-platform-info';


const MAC_APP_NAME = 'TestCafe Browser Tools.app';
const MAC_BINARY_PATH = binary => join(__dirname, `../bin/mac/${MAC_APP_NAME}/Contents/MacOS/${binary}`);

var BINARIES = void 0;

if (OS.win) {
Expand All @@ -19,17 +23,18 @@ if (OS.win) {
}
else if (OS.mac) {
BINARIES = {
open: toAbsPath('../bin/mac/open.scpt'),
findWindow: toAbsPath('../bin/mac/find-window'),
getWindowSize: toAbsPath('../bin/mac/get-window-size'),
getWindowBounds: toAbsPath('../bin/mac/get-window-bounds'),
getWindowMaxBounds: toAbsPath('../bin/mac/get-window-max-bounds'),
setWindowBounds: toAbsPath('../bin/mac/set-window-bounds'),
close: toAbsPath('../bin/mac/close'),
screenshot: toAbsPath('../bin/mac/screenshot'),
resize: toAbsPath('../bin/mac/resize'),
generateThumbnail: toAbsPath('../bin/mac/generate-thumbnail'),
bringToFront: toAbsPath('../bin/mac/bring-to-front')
app: MAC_BINARY_PATH('testcafe-browser-tools'),
open: 'open',
findWindow: 'find-window',
getWindowSize: 'get-window-size',
getWindowBounds: 'get-window-bounds',
getWindowMaxBounds: 'get-window-max-bounds',
setWindowBounds: 'set-window-bounds',
close: 'close',
screenshot: 'screenshot',
resize: 'resize',
generateThumbnail: 'generate-thumbnail',
bringToFront: 'bring-to-front'
};
}
else if (OS.linux) {
Expand Down
6 changes: 6 additions & 0 deletions src/natives/app/mac/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
all: clean build
clean:
rm "${DEST}/testcafe-browser-tools" || true
build:
mkdir "${DEST}" || true
clang -o "${DEST}/testcafe-browser-tools" -framework Cocoa -framework ScriptingBridge -framework AppKit ../../*/{mac,any}/obj/*.o testcafe-browser-tools.m
66 changes: 66 additions & 0 deletions src/natives/app/mac/testcafe-browser-tools.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// testcafe-browser-tools.m
// Dispatch browser manipulation commands
//

#include <fcntl.h>
#import <Cocoa/Cocoa.h>

typedef int command (int argc, const char * argv[]);

int bringToFront (int argc, const char * argv[]);
int closeWindow (int argc, const char * argv[]);
int findWindow (int argc, const char * argv[]);
int generateThumbnail (int argc, const char * argv[]);
int getWindowBounds (int argc, const char * argv[]);
int getWindowMaxBounds (int argc, const char * argv[]);
int getWindowSize (int argc, const char * argv[]);
int openWindow (int argc, const char * argv[]);
int resize (int argc, const char * argv[]);
int screenshot (int argc, const char * argv[]);
int setWindowBounds (int argc, const char * argv[]);

int main (int argc, const char * argv[]) {
int fd = open(argv[1], O_WRONLY);

if (fd != -1)
dup2(fd, STDOUT_FILENO);

@autoreleasepool {
id commands = @{
@"bring-to-front": [NSValue valueWithPointer: &bringToFront],
@"close": [NSValue valueWithPointer: &closeWindow],
@"find-window": [NSValue valueWithPointer: &findWindow],
@"generate-thumbnail": [NSValue valueWithPointer: &generateThumbnail],
@"get-window-bounds": [NSValue valueWithPointer: &getWindowBounds],
@"get-window-max-bounds": [NSValue valueWithPointer: &getWindowMaxBounds],
@"get-window-size": [NSValue valueWithPointer: &getWindowSize],
@"open": [NSValue valueWithPointer: &openWindow],
@"resize": [NSValue valueWithPointer: &resize],
@"screenshot": [NSValue valueWithPointer: &screenshot],
@"set-window-bounds": [NSValue valueWithPointer: &setWindowBounds],

};

id commandName = [NSString stringWithUTF8String:argv[2]];

id item = commands[commandName];

if (item == nil)
return 1;

command *commandPointer = (command *)[item pointerValue];

int exitCode = (*commandPointer)(argc - 2, argv + 2);

printf("\nExit code: %d\n", exitCode);

if (fd != -1) {
fsync(fd);
close(fd);
}
}

return 0;
}

4 changes: 2 additions & 2 deletions src/natives/bring-to-front/mac/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
all: clean build
clean:
rm "${DEST}/bring-to-front" || true
rm "${DEST}/bring-to-front.o" || true
build:
mkdir "${DEST}" || true
clang -o "${DEST}/bring-to-front" -framework Cocoa -framework ScriptingBridge -framework AppKit bring-to-front.m
clang -c -o "${DEST}/bring-to-front.o" bring-to-front.m
2 changes: 1 addition & 1 deletion src/natives/bring-to-front/mac/bring-to-front.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

const int APP_ACTIVATION_DELAY = 100000;

int main (int argc, const char * argv[]) {
int bringToFront (int argc, const char * argv[]) {
if (argc < 3) {
printf("Incorrect arguments\n");
return 1;
Expand Down
4 changes: 2 additions & 2 deletions src/natives/close/mac/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
all: clean build
clean:
rm "${DEST}/close" || true
rm "${DEST}/close.o" || true
build:
mkdir "${DEST}" || true
clang -o "${DEST}/close" -framework Cocoa -framework ScriptingBridge close.m
clang -c -o "${DEST}/close.o" close.m
2 changes: 1 addition & 1 deletion src/natives/close/mac/close.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
CloseOptionsAsk = 'ask ' /* Ask the user whether or not to save the file. */
};

int main (int argc, const char * argv[]) {
int closeWindow (int argc, const char * argv[]) {
if (argc < 3) {
printf("Incorrect arguments\n");
return 1;
Expand Down
4 changes: 2 additions & 2 deletions src/natives/find-window/mac/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
all: clean build
clean:
rm "${DEST}/find-window" || true
rm "${DEST}/find-window.o" || true
build:
mkdir "${DEST}" || true
clang -o "${DEST}/find-window" -framework Cocoa -framework ScriptingBridge find-window.m
clang -c -o "${DEST}/find-window.o" find-window.m
Loading

0 comments on commit 9002d21

Please sign in to comment.