Skip to content

Commit bc94c60

Browse files
Merge pull request #207 from browserstack/CYP-912-IP-Geolocation
IP Geolocation
2 parents d322fbe + 4e779cb commit bc94c60

File tree

8 files changed

+313
-1
lines changed

8 files changed

+313
-1
lines changed

bin/commands/runs.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ module.exports = function run(args, rawArgs) {
4848
// set cypress config filename
4949
utils.setCypressConfigFilename(bsConfig, args);
5050

51+
// set cypress geo location
52+
utils.setGeolocation(bsConfig, args);
53+
5154
// accept the specs list from command line if provided
5255
utils.setUserSpecs(bsConfig, args);
5356

@@ -236,6 +239,9 @@ module.exports = function run(args, rawArgs) {
236239
utils.sendUsageReport(bsConfig, args, `${message}\n${dashboardLink}`, Constants.messageTypes.SUCCESS, null, buildReportData, rawArgs);
237240
return;
238241
}).catch(async function (err) {
242+
if (err && err.includes('browserstack.geoLocation')) {
243+
err = err.replace(/browserstack.geoLocation/g, 'geolocation');
244+
}
239245
// Build creation failed
240246
logger.error(err);
241247
// stop the Local instance

bin/helpers/capabilityHelper.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ const validate = (bsConfig, args) => {
173173

174174
if( Utils.searchForOption('--async') && ( !Utils.isUndefined(args.async) && bsConfig["connection_settings"]["local"])) reject(Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS);
175175

176+
if (bsConfig.run_settings.userProvidedGeolocation && !bsConfig.run_settings.geolocation.match(/^[A-Z]{2}$/g)) reject(Constants.validationMessages.INVALID_GEO_LOCATION);
177+
178+
if (bsConfig["connection_settings"]["local"] && bsConfig.run_settings.userProvidedGeolocation) reject(Constants.validationMessages.NOT_ALLOWED_GEO_LOCATION_AND_LOCAL_MODE);
179+
176180
// validate if config file provided exists or not when cypress_config_file provided
177181
// validate the cypressProjectDir key otherwise.
178182
let cypressConfigFilePath = bsConfig.run_settings.cypressConfigFilePath;

bin/helpers/constants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ const validationMessages = {
8787
INVALID_LOCAL_IDENTIFIER: "Invalid value specified for local_identifier. For more info, check out https://www.browserstack.com/docs/automate/cypress/cli-reference",
8888
INVALID_BROWSER_ARGS: "Aborting as an unacceptable value was passed for --browser. Read more at https://www.browserstack.com/docs/automate/cypress/cli-reference",
8989
INVALID_LOCAL_ASYNC_ARGS: "Cannot run in --async mode when local is set to true. Please run the build after removing --async",
90+
INVALID_GEO_LOCATION: "[BROWSERSTACK_INVALID_COUNTRY_CODE] The country code specified for 'geolocation' is invalid. For list of supported countries, refer to - https://www.browserstack.com/ip-geolocation",
91+
NOT_SUPPORTED_GEO_LOCATION: "The country code you have passed for IP Geolocation is currently not supported. Please refer the link https://www.browserstack.com/ip-geolocation for a list of supported countries.",
92+
NOT_AVAILABLE_GEO_LOCATION: "The country code you have passed for IP Geolocation is not available at the moment. Please try again in a few hours.",
93+
ACCESS_DENIED_GEO_LOCATION: "'geolocation' (IP Geolocation feature) capability is not supported in your account. It is only available under Enterprise plans, refer https://www.browserstack.com/ip-geolocation for more details.",
94+
NOT_ALLOWED_GEO_LOCATION_AND_LOCAL_MODE: "IP Geolocation feature is not available in conjunction with BrowserStack Local.",
9095
HOME_DIRECTORY_NOT_FOUND: "Specified home directory could not be found. Please make sure the path of the home directory is correct.",
9196
HOME_DIRECTORY_NOT_A_DIRECTORY: "Specified home directory is not a directory. The home directory can only be a directory and not a file.",
9297
CYPRESS_CONFIG_FILE_NOT_PART_OF_HOME_DIRECTORY: "Could not find cypress.json within the specified home directory. Please make sure cypress.json resides within the home directory."
@@ -132,6 +137,7 @@ const cliMessages = {
132137
CONFIG_DESCRIPTION: "Set configuration values. Separate multiple values with a comma. The values set here override any values set in your configuration file.",
133138
REPORTER: "Specify the custom reporter to use",
134139
REPORTER_OPTIONS: "Specify reporter options for custom reporter",
140+
CYPRESS_GEO_LOCATION: "Enterprise feature to simulate website and mobile behavior from different locations."
135141
},
136142
COMMON: {
137143
DISABLE_USAGE_REPORTING: "Disable usage reporting",

bin/helpers/utils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ exports.getErrorCodeFromMsg = (errMsg) => {
8585
case Constants.validationMessages.INVALID_LOCAL_ASYNC_ARGS:
8686
errorCode = 'invalid_local_async_args';
8787
break;
88+
case Constants.validationMessages.INVALID_GEO_LOCATION:
89+
errorCode = 'invalid_geo_location';
90+
break;
91+
case Constants.validationMessages.NOT_ALLOWED_GEO_LOCATION_AND_LOCAL_MODE:
92+
errorCode = 'not_allowed_geo_location_and_local_mode';
93+
break;
8894
case Constants.validationMessages.HOME_DIRECTORY_NOT_FOUND:
8995
errorCode = 'home_directory_not_found';
9096
break;
@@ -281,6 +287,28 @@ exports.setCypressConfigFilename = (bsConfig, args) => {
281287
}
282288
}
283289

290+
exports.verifyGeolocationOption = () => {
291+
let glOptionsSet = (this.searchForOption('-gl') || this.searchForOption('--gl'));
292+
let geoHyphenLocationOptionsSet = (this.searchForOption('-geo-location') || this.searchForOption('--geo-location'));
293+
let geoLocationOptionsSet = (this.searchForOption('-geolocation') || this.searchForOption('--geolocation'));
294+
return (glOptionsSet || geoHyphenLocationOptionsSet || geoLocationOptionsSet);
295+
}
296+
297+
exports.setGeolocation = (bsConfig, args) => {
298+
let userProvidedGeolocation = this.verifyGeolocationOption();
299+
bsConfig.run_settings.userProvidedGeolocation = (userProvidedGeolocation || (!this.isUndefined(bsConfig.run_settings.geolocation)));
300+
301+
if (userProvidedGeolocation && !this.isUndefined(args.geolocation)) {
302+
bsConfig.run_settings.geolocation = args.geolocation;
303+
}
304+
305+
if (this.isUndefined(bsConfig.run_settings.geolocation)){
306+
bsConfig.run_settings.geolocation = null;
307+
} else {
308+
bsConfig.run_settings.geolocation = bsConfig.run_settings.geolocation.toUpperCase();
309+
}
310+
}
311+
284312
// specs can be passed from bstack configuration file
285313
// specs can be passed via command line args as a string
286314
// command line args takes precedence over config

bin/runner.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ var argv = yargs
133133
demand: true,
134134
demand: Constants.cliMessages.RUN.CYPRESS_CONFIG_DEMAND
135135
},
136+
'gl': {
137+
alias: 'geolocation',
138+
describe: Constants.cliMessages.RUN.CYPRESS_GEO_LOCATION,
139+
default: undefined,
140+
type: 'string'
141+
},
136142
'p': {
137143
alias: ['parallels', 'parallel'],
138144
describe: Constants.cliMessages.RUN.PARALLEL_DESC,

test/unit/bin/commands/runs.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ describe("runs", () => {
115115
setBrowsersStub = sandbox.stub();
116116
setConfigStub = sandbox.stub();
117117
setCLIModeStub = sandbox.stub();
118+
setGeolocationStub = sandbox.stub();
118119
});
119120

120121
afterEach(() => {
@@ -154,7 +155,8 @@ describe("runs", () => {
154155
setSystemEnvs: setSystemEnvsStub,
155156
setBrowsers: setBrowsersStub,
156157
setConfig: setConfigStub,
157-
setCLIMode: setCLIModeStub
158+
setCLIMode: setCLIModeStub,
159+
setGeolocation: setGeolocationStub
158160
},
159161
'../helpers/capabilityHelper': {
160162
validate: capabilityValidatorStub
@@ -194,6 +196,7 @@ describe("runs", () => {
194196
sinon.assert.calledOnce(getErrorCodeFromMsgStub);
195197
sinon.assert.calledOnce(setLocalIdentifierStub);
196198
sinon.assert.calledOnce(setUsageReportingFlagStub);
199+
sinon.assert.calledOnce(setGeolocationStub);
197200
sinon.assert.calledOnceWithExactly(
198201
sendUsageReportStub,
199202
bsConfig,
@@ -249,6 +252,7 @@ describe("runs", () => {
249252
setBrowsersStub = sandbox.stub();
250253
setConfigStub = sandbox.stub();
251254
setCLIModeStub = sandbox.stub();
255+
setGeolocationStub = sandbox.stub();
252256
getVideoConfigStub = sandbox.stub();
253257
});
254258

@@ -292,6 +296,7 @@ describe("runs", () => {
292296
setBrowsers: setBrowsersStub,
293297
setConfig: setConfigStub,
294298
setCLIMode: setCLIModeStub,
299+
setGeolocation: setGeolocationStub,
295300
getVideoConfig: getVideoConfigStub,
296301
},
297302
'../helpers/capabilityHelper': {
@@ -351,6 +356,7 @@ describe("runs", () => {
351356
sinon.assert.calledOnce(deleteResultsStub);
352357
sinon.assert.calledOnce(setDefaultsStub);
353358
sinon.assert.calledOnce(setSystemEnvsStub);
359+
sinon.assert.calledOnce(setGeolocationStub);
354360
sinon.assert.calledOnceWithExactly(
355361
sendUsageReportStub,
356362
bsConfig,
@@ -408,6 +414,7 @@ describe("runs", () => {
408414
setBrowsersStub = sandbox.stub();
409415
setCLIModeStub = sandbox.stub();
410416
fetchZipSizeStub = sandbox.stub();
417+
setGeolocationStub = sandbox.stub();
411418
getVideoConfigStub = sandbox.stub();
412419
});
413420

@@ -452,6 +459,7 @@ describe("runs", () => {
452459
setConfig: setConfigStub,
453460
setCLIMode: setCLIModeStub,
454461
fetchZipSize: fetchZipSizeStub,
462+
setGeolocation: setGeolocationStub,
455463
getVideoConfig: getVideoConfigStub,
456464
},
457465
'../helpers/capabilityHelper': {
@@ -513,6 +521,7 @@ describe("runs", () => {
513521
sinon.assert.calledOnce(deleteResultsStub);
514522
sinon.assert.calledOnce(setDefaultsStub);
515523
sinon.assert.calledOnce(setSystemEnvsStub);
524+
sinon.assert.calledOnce(setGeolocationStub);
516525
sinon.assert.calledOnceWithExactly(
517526
sendUsageReportStub,
518527
bsConfig,
@@ -575,6 +584,7 @@ describe("runs", () => {
575584
setBrowsersStub = sandbox.stub();
576585
setCLIModeStub = sandbox.stub();
577586
fetchZipSizeStub = sandbox.stub();
587+
setGeolocationStub = sandbox.stub();
578588
getVideoConfigStub = sandbox.stub();
579589
});
580590

@@ -620,6 +630,7 @@ describe("runs", () => {
620630
setConfig: setConfigStub,
621631
setCLIMode: setCLIModeStub,
622632
fetchZipSize: fetchZipSizeStub,
633+
setGeolocation: setGeolocationStub,
623634
getVideoConfig: getVideoConfigStub,
624635
},
625636
'../helpers/capabilityHelper': {
@@ -692,6 +703,7 @@ describe("runs", () => {
692703
sinon.assert.calledOnce(deleteResultsStub);
693704
sinon.assert.calledOnce(setDefaultsStub);
694705
sinon.assert.calledOnce(setSystemEnvsStub);
706+
sinon.assert.calledOnce(setGeolocationStub);
695707

696708
sinon.assert.calledOnceWithExactly(
697709
sendUsageReportStub,
@@ -768,6 +780,7 @@ describe("runs", () => {
768780
setCLIModeStub = sandbox.stub();
769781
setProcessHooksStub = sandbox.stub();
770782
fetchZipSizeStub = sandbox.stub();
783+
setGeolocationStub = sandbox.stub();
771784
getVideoConfigStub = sandbox.stub();
772785
});
773786

@@ -821,6 +834,7 @@ describe("runs", () => {
821834
setCLIMode: setCLIModeStub,
822835
setProcessHooks: setProcessHooksStub,
823836
fetchZipSize: fetchZipSizeStub,
837+
setGeolocation: setGeolocationStub,
824838
getVideoConfig: getVideoConfigStub,
825839
},
826840
'../helpers/capabilityHelper': {
@@ -910,6 +924,7 @@ describe("runs", () => {
910924
sinon.assert.calledOnce(deleteResultsStub);
911925
sinon.assert.calledOnce(setDefaultsStub);
912926
sinon.assert.calledOnce(setSystemEnvsStub);
927+
sinon.assert.calledOnce(setGeolocationStub);
913928
sinon.assert.match(
914929
sendUsageReportStub.getCall(0).args,
915930
[

test/unit/bin/helpers/capabilityHelper.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ describe("capabilityHelper.js", () => {
586586
cypress_proj_dir: "random path",
587587
cypressConfigFilePath: "random path"
588588
},
589+
connection_settings: {local: false}
589590
};
590591
});
591592

@@ -952,6 +953,7 @@ describe("capabilityHelper.js", () => {
952953
cypressConfigFilePath: "random path",
953954
cypressProjectDir: "random path"
954955
},
956+
connection_settings: {local: false}
955957
};
956958
});
957959
it("validate cypress json is present", () => {
@@ -1064,6 +1066,99 @@ describe("capabilityHelper.js", () => {
10641066
})
10651067
});
10661068
});
1069+
1070+
describe("validate ip geolocation", () => {
1071+
beforeEach(() => {
1072+
bsConfig = {
1073+
auth: {},
1074+
browsers: [
1075+
{
1076+
browser: "chrome",
1077+
os: "Windows 10",
1078+
versions: ["78", "77"],
1079+
},
1080+
],
1081+
run_settings: {
1082+
cypress_proj_dir: "random path",
1083+
cypressConfigFilePath: "random path",
1084+
cypressProjectDir: "random path"
1085+
},
1086+
connection_settings: {}
1087+
};
1088+
});
1089+
1090+
it("should throw an error if both local and geolocation are used", () => {
1091+
bsConfig.run_settings.geolocation = "US";
1092+
bsConfig.run_settings.userProvidedGeolocation = true;
1093+
bsConfig.connection_settings.local = true;
1094+
bsConfig.connection_settings.local_identifier = "some text";
1095+
1096+
return capabilityHelper
1097+
.validate(bsConfig, {})
1098+
.then(function (data) {
1099+
chai.assert.fail("Promise error");
1100+
})
1101+
.catch((error) => {
1102+
chai.assert.equal(error, Constants.validationMessages.NOT_ALLOWED_GEO_LOCATION_AND_LOCAL_MODE);
1103+
});
1104+
});
1105+
1106+
it("should throw an error if incorrect format for geolocation code is used (valid country name but incorrect code)", () => {
1107+
bsConfig.run_settings.geolocation = "USA";
1108+
bsConfig.run_settings.userProvidedGeolocation = true;
1109+
1110+
return capabilityHelper
1111+
.validate(bsConfig, {})
1112+
.then(function (data) {
1113+
chai.assert.fail("Promise error");
1114+
})
1115+
.catch((error) => {
1116+
chai.assert.equal(error, Constants.validationMessages.INVALID_GEO_LOCATION);
1117+
});
1118+
});
1119+
1120+
it("should throw an error if incorrect format for geolocation code is used (random value)", () => {
1121+
bsConfig.run_settings.geolocation = "RANDOM";
1122+
bsConfig.run_settings.userProvidedGeolocation = true;
1123+
1124+
return capabilityHelper
1125+
.validate(bsConfig, {})
1126+
.then(function (data) {
1127+
chai.assert.fail("Promise error");
1128+
})
1129+
.catch((error) => {
1130+
chai.assert.equal(error, Constants.validationMessages.INVALID_GEO_LOCATION);
1131+
});
1132+
});
1133+
1134+
it("should throw an error if incorrect format for geolocation code is used (special chars)", () => {
1135+
bsConfig.run_settings.geolocation = "$USA$!&@*)()";
1136+
bsConfig.run_settings.userProvidedGeolocation = true;
1137+
1138+
return capabilityHelper
1139+
.validate(bsConfig, {})
1140+
.then(function (data) {
1141+
chai.assert.fail("Promise error");
1142+
})
1143+
.catch((error) => {
1144+
chai.assert.equal(error, Constants.validationMessages.INVALID_GEO_LOCATION);
1145+
});
1146+
});
1147+
1148+
it("should throw an error if incorrect format for geolocation code is used (small caps)", () => {
1149+
bsConfig.run_settings.geolocation = "us";
1150+
bsConfig.run_settings.userProvidedGeolocation = true;
1151+
1152+
return capabilityHelper
1153+
.validate(bsConfig, {})
1154+
.then(function (data) {
1155+
chai.assert.fail("Promise error");
1156+
})
1157+
.catch((error) => {
1158+
chai.assert.equal(error, Constants.validationMessages.INVALID_GEO_LOCATION);
1159+
});
1160+
});
1161+
});
10671162

10681163
describe("validate home directory", () => {
10691164
beforeEach(() => {
@@ -1081,6 +1176,7 @@ describe("capabilityHelper.js", () => {
10811176
cypressConfigFilePath: "random path",
10821177
cypressProjectDir: "random path"
10831178
},
1179+
connection_settings: {local: false}
10841180
};
10851181
});
10861182

0 commit comments

Comments
 (0)