From 9cadc63f1899beba4b107e7e10ff267eb1d04fc0 Mon Sep 17 00:00:00 2001 From: Ryan Rehman Date: Thu, 10 Aug 2023 14:04:32 +0530 Subject: [PATCH 01/25] Check entire binary name for download step. --- browserstack/local_binary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 4bec7e7..130bd44 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -102,7 +102,8 @@ def get_binary(self): dest_parent_dir = os.path.join(os.path.expanduser('~'), '.browserstack') if not os.path.exists(dest_parent_dir): os.makedirs(dest_parent_dir) - bsfiles = [f for f in os.listdir(dest_parent_dir) if f.startswith('BrowserStackLocal')] + binary_name = 'BrowserStackLocal.exe' if self.is_windows else 'BrowserStackLocal' + bsfiles = [f for f in os.listdir(dest_parent_dir) if f == binary_name] if len(bsfiles) == 0: binary_path = self.download() From 68cdae2347842f8e47de5a7292edce8ed169beda Mon Sep 17 00:00:00 2001 From: Ryan Rehman Date: Fri, 18 Aug 2023 17:06:11 +0530 Subject: [PATCH 02/25] Version bump to 1.2.7 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c6a76fc..d28e2d1 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.6', + version = '1.2.7', description = 'Python bindings for Browserstack Local', author = 'BrowserStack', author_email = 'support@browserstack.com', From 318af207ace25ba338971df5536026bf53cfdaf7 Mon Sep 17 00:00:00 2001 From: AdityaHirapara Date: Tue, 30 Jul 2024 14:51:27 +0530 Subject: [PATCH 03/25] Fix alpine check command --- browserstack/local_binary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 130bd44..3f87ead 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -33,7 +33,7 @@ def __init__(self): self.path_index = 0 def is_alpine(self): - grepOutput = subprocess.run("gfrep -w NAME /etc/os-release", capture_output=True, shell=True) + grepOutput = subprocess.run("grep -w NAME /etc/os-release", capture_output=True, shell=True) if grepOutput.stdout.decode('utf-8').find('Alpine') > -1: return True return False From b7895debeab7ce27ac65fb6d35ccbf88018837e1 Mon Sep 17 00:00:00 2001 From: AdityaHirapara Date: Thu, 8 Aug 2024 16:30:12 +0530 Subject: [PATCH 04/25] Bump up the version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d28e2d1..f8bd088 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.7', + version = '1.2.8', description = 'Python bindings for Browserstack Local', author = 'BrowserStack', author_email = 'support@browserstack.com', From e7642c649e527abdc4a7b5f28ffe5b74884e07de Mon Sep 17 00:00:00 2001 From: "Yash D. Saraf" Date: Fri, 9 Aug 2024 16:53:30 +0530 Subject: [PATCH 05/25] Add long description to local python binding --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index f8bd088..1c9b2a1 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,8 @@ packages = ['browserstack'], version = '1.2.8', description = 'Python bindings for Browserstack Local', + long_description=open('README.md').read(), + long_description_content_type='text/markdown', author = 'BrowserStack', author_email = 'support@browserstack.com', url = 'https://github.com/browserstack/browserstack-local-python', From bed2dfe579cf667ea774ae918ee445d5712454c7 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 7 Oct 2024 13:42:16 +0530 Subject: [PATCH 06/25] feat: add gzip support, send UA containing version --- browserstack/local.py | 1 + browserstack/local_binary.py | 57 +++++++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/browserstack/local.py b/browserstack/local.py index d7e1618..65c9549 100644 --- a/browserstack/local.py +++ b/browserstack/local.py @@ -15,6 +15,7 @@ def __init__(self, key=None, binary_path=None, **kwargs): self.key = os.environ['BROWSERSTACK_ACCESS_KEY'] if 'BROWSERSTACK_ACCESS_KEY' in os.environ else key self.options = kwargs self.local_logfile_path = os.path.join(os.getcwd(), 'local.log') + LocalBinary.set_version(self.get_package_version()) def __xstr(self, key, value): if key is None: diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 3f87ead..fad6fbc 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -1,12 +1,21 @@ import platform, os, sys, stat, tempfile, re, subprocess from browserstack.bserrors import BrowserStackLocalError +import gzip try: - from urllib.request import urlopen + import urllib.request + + def urlopen(url, headers=None): + return urllib.request.urlopen(urllib.request.Request(url, headers=headers)) except ImportError: - from urllib2 import urlopen + import urllib2 + + def urlopen(url, headers=None): + return urllib2.urlopen(urllib2.Request(url, headers=headers)) class LocalBinary: + _version = None + def __init__(self): is_64bits = sys.maxsize > 2**32 self.is_windows = False @@ -32,11 +41,13 @@ def __init__(self): ] self.path_index = 0 + @staticmethod + def set_version(version): + LocalBinary._version = version + def is_alpine(self): - grepOutput = subprocess.run("grep -w NAME /etc/os-release", capture_output=True, shell=True) - if grepOutput.stdout.decode('utf-8').find('Alpine') > -1: - return True - return False + response = subprocess.check_output(["grep", "-w", "NAME", "/etc/os-release"]) + return response.decode('utf-8').find('Alpine') > -1 def __make_path(self, dest_path): try: @@ -57,11 +68,20 @@ def __available_dir(self): raise BrowserStackLocalError('Error trying to download BrowserStack Local binary') def download(self, chunk_size=8192, progress_hook=None): - response = urlopen(self.http_path) + headers = { + 'User-Agent': '/'.join(('browserstack-local-python', LocalBinary._version)), + 'Accept-Encoding': 'gzip, *', + } + + if sys.version_info < (3, 2): + # lack of support for gzip decoding for stream, response is expected to have a tell() method + headers.pop('Accept-Encoding', None) + + response = urlopen(self.http_path, headers=headers) try: - total_size = int(response.info().getheader('Content-Length').strip()) + total_size = int(response.info().get('Content-Length', '').strip() or '0') except: - total_size = int(response.info().get_all('Content-Length')[0].strip()) + total_size = int(response.info().get_all('Content-Length')[0].strip() or '0') bytes_so_far = 0 dest_parent_dir = self.__available_dir() @@ -69,21 +89,36 @@ def download(self, chunk_size=8192, progress_hook=None): if self.is_windows: dest_binary_name += '.exe' + content_encoding = response.info().get('Content-Encoding', '') + gzip_file = gzip.GzipFile(fileobj=response, mode='rb') if content_encoding.lower() == 'gzip' else None + + def read_chunk(chunk_size): + if gzip_file: + return gzip_file.read(chunk_size) + else: + return response.read(chunk_size) + with open(os.path.join(dest_parent_dir, dest_binary_name), 'wb') as local_file: while True: - chunk = response.read(chunk_size) + chunk = read_chunk(chunk_size) bytes_so_far += len(chunk) if not chunk: break - if progress_hook: + if total_size > 0 and progress_hook: progress_hook(bytes_so_far, chunk_size, total_size) try: local_file.write(chunk) except: return self.download(chunk_size, progress_hook) + + if gzip_file: + gzip_file.close() + + if callable(getattr(response, 'close', None)): + response.close() final_path = os.path.join(dest_parent_dir, dest_binary_name) st = os.stat(final_path) From ac36e9e13e5b9f45551c815738bdb40bf04d97f4 Mon Sep 17 00:00:00 2001 From: Shirish Kamath Date: Mon, 7 Oct 2024 13:46:59 +0530 Subject: [PATCH 07/25] chore: apply sourceURL override, log line for debugging --- browserstack/local_binary.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index fad6fbc..77f878a 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -20,19 +20,23 @@ def __init__(self): is_64bits = sys.maxsize > 2**32 self.is_windows = False osname = platform.system() + source_url = "https://www.browserstack.com/local-testing/downloads/binaries/" + if os.environ.get('BROWSERSTACK_LOCAL_BIN_URL'): + source_url = os.environ.get('BROWSERSTACK_LOCAL_BIN_URL') + if osname == 'Darwin': - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-darwin-x64" + self.http_path = source_url + "BrowserStackLocal-darwin-x64" elif osname == 'Linux': if self.is_alpine(): - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-alpine" + self.http_path = source_url + "BrowserStackLocal-alpine" else: if is_64bits: - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-linux-x64" + self.http_path = source_url + "BrowserStackLocal-linux-x64" else: - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal-linux-ia32" + self.http_path = source_url + "BrowserStackLocal-linux-ia32" else: self.is_windows = True - self.http_path = "https://www.browserstack.com/local-testing/downloads/binaries/BrowserStackLocal.exe" + self.http_path = source_url + "BrowserStackLocal.exe" self.ordered_paths = [ os.path.join(os.path.expanduser('~'), '.browserstack'), @@ -92,6 +96,9 @@ def download(self, chunk_size=8192, progress_hook=None): content_encoding = response.info().get('Content-Encoding', '') gzip_file = gzip.GzipFile(fileobj=response, mode='rb') if content_encoding.lower() == 'gzip' else None + if os.getenv('BROWSERSTACK_LOCAL_DEBUG_GZIP') and gzip_file: + print('using gzip in ' + headers['User-Agent']) + def read_chunk(chunk_size): if gzip_file: return gzip_file.read(chunk_size) From 4db9ac9d4b7e29642f3a8ec707cd10a3727c8217 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Wed, 16 Oct 2024 23:28:00 +0530 Subject: [PATCH 08/25] fix: expected str, bytes or os.PathLike object, not int - in case of httpPort --- browserstack/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browserstack/local.py b/browserstack/local.py index d7e1618..a1b3397 100644 --- a/browserstack/local.py +++ b/browserstack/local.py @@ -24,7 +24,7 @@ def __xstr(self, key, value): elif str(value).lower() == "false": return [''] else: - return ['-' + key, value] + return ['-' + key, str(value)] def get_package_version(self): name = "browserstack-local" From 0b6a74961c2282bc5ffc284b6efb94a6a4edbd5b Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Thu, 24 Oct 2024 12:59:33 +0530 Subject: [PATCH 09/25] update: version bump local-python 1.2.9 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1c9b2a1..045a6b4 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.8', + version = '1.2.9', description = 'Python bindings for Browserstack Local', long_description=open('README.md').read(), long_description_content_type='text/markdown', From 29d9194a5a8dc2a28d08c66a7f5da880019f8298 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 15:40:18 +0530 Subject: [PATCH 10/25] update: remove URL flag --- browserstack/local_binary.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 77f878a..870501b 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -21,8 +21,6 @@ def __init__(self): self.is_windows = False osname = platform.system() source_url = "https://www.browserstack.com/local-testing/downloads/binaries/" - if os.environ.get('BROWSERSTACK_LOCAL_BIN_URL'): - source_url = os.environ.get('BROWSERSTACK_LOCAL_BIN_URL') if osname == 'Darwin': self.http_path = source_url + "BrowserStackLocal-darwin-x64" From fd3d938e016c28d4523d2c7adf4ea56caaf4296f Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 16:21:26 +0530 Subject: [PATCH 11/25] chore: remove refactoring --- browserstack/local_binary.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 870501b..7a13d85 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -3,15 +3,9 @@ import gzip try: - import urllib.request - - def urlopen(url, headers=None): - return urllib.request.urlopen(urllib.request.Request(url, headers=headers)) + from urllib.request import urlopen except ImportError: - import urllib2 - - def urlopen(url, headers=None): - return urllib2.urlopen(urllib2.Request(url, headers=headers)) + from urllib2 import urlopen class LocalBinary: _version = None From de1af8f822e52f0f02cd9ae1e15cf77ad24bcabb Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 16:22:49 +0530 Subject: [PATCH 12/25] chore: remove refactoring --- browserstack/local_binary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 7a13d85..b39b4c4 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -3,9 +3,9 @@ import gzip try: - from urllib.request import urlopen + from urllib.request import urlopen except ImportError: - from urllib2 import urlopen + from urllib2 import urlopen class LocalBinary: _version = None From e5f72a570887e020f6579b5c3eee33e1973e8985 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 16:31:38 +0530 Subject: [PATCH 13/25] update: add request object --- browserstack/local_binary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index b39b4c4..72d1402 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -3,9 +3,9 @@ import gzip try: - from urllib.request import urlopen + from urllib.request import urlopen, Request except ImportError: - from urllib2 import urlopen + from urllib2 import urlopen, Request class LocalBinary: _version = None @@ -73,7 +73,7 @@ def download(self, chunk_size=8192, progress_hook=None): # lack of support for gzip decoding for stream, response is expected to have a tell() method headers.pop('Accept-Encoding', None) - response = urlopen(self.http_path, headers=headers) + response = urlopen(Request(self.http_path, headers=headers)) try: total_size = int(response.info().get('Content-Length', '').strip() or '0') except: From 8374ce64485b0b9abbae49d06cf98e4b40d27422 Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 17:20:12 +0530 Subject: [PATCH 14/25] update: version bump v1.2.10 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 045a6b4..83ea1ef 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.9', + version = '1.2.10', description = 'Python bindings for Browserstack Local', long_description=open('README.md').read(), long_description_content_type='text/markdown', From e3362ff8b2c6e74938df53fc58c15c713937c4ba Mon Sep 17 00:00:00 2001 From: Kamalpreet Kaur Date: Mon, 23 Dec 2024 18:18:28 +0530 Subject: [PATCH 15/25] fix: tests --- tests/test_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_local.py b/tests/test_local.py index 11d6a02..d5d4e46 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -70,7 +70,7 @@ def test_proxy(self): self.assertIn('-proxyHost', self.local._generate_cmd()) self.assertIn('localhost', self.local._generate_cmd()) self.assertIn('-proxyPort', self.local._generate_cmd()) - self.assertIn(2000, self.local._generate_cmd()) + self.assertIn('2000', self.local._generate_cmd()) self.assertIn('-proxyUser', self.local._generate_cmd()) self.assertIn('hello', self.local._generate_cmd()) self.assertIn('-proxyPass', self.local._generate_cmd()) From 187fb9b8b234b8be6bec39922d0e99935eafa6f2 Mon Sep 17 00:00:00 2001 From: amaanbs Date: Fri, 6 Jun 2025 07:12:26 +0530 Subject: [PATCH 16/25] change binary download distribution --- browserstack/local.py | 7 ++++++- browserstack/local_binary.py | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/browserstack/local.py b/browserstack/local.py index 3dd5d1a..544c791 100644 --- a/browserstack/local.py +++ b/browserstack/local.py @@ -73,7 +73,12 @@ def start(self, **kwargs): self.binary_path = self.options['binarypath'] del self.options['binarypath'] else: - self.binary_path = LocalBinary().get_binary() + l = LocalBinary(self.key) + try: + self.binary_path = l.get_binary() + except Exception as e: + l = LocalBinary(self.key, e) + self.binary_path = l.get_binary() if 'logfile' in self.options: self.local_logfile_path = self.options['logfile'] diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 72d1402..11c6ef0 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -1,6 +1,7 @@ import platform, os, sys, stat, tempfile, re, subprocess from browserstack.bserrors import BrowserStackLocalError import gzip +import json try: from urllib.request import urlopen, Request @@ -10,11 +11,13 @@ class LocalBinary: _version = None - def __init__(self): + def __init__(self, key, error_object=None): + self.key = key + self.error_object = error_object is_64bits = sys.maxsize > 2**32 self.is_windows = False osname = platform.system() - source_url = "https://www.browserstack.com/local-testing/downloads/binaries/" + source_url = self.fetch_source_url() + '/' if osname == 'Darwin': self.http_path = source_url + "BrowserStackLocal-darwin-x64" @@ -37,6 +40,31 @@ def __init__(self): ] self.path_index = 0 + def fetch_source_url(self): + url = "https://local.browserstack.com/binary/api/v1/endpoint" + headers = { + "Content-Type": "application/json", + "Accept": "application/json" + } + data = {"auth_token": self.key} + + if self.error_object is not None: + data["error_message"] = str(self.error_object) + headers["X-Local-Fallback-Cloudflare"] = "true" + + req = Request(url, data=json.dumps(data).encode("utf-8")) + for key, value in headers.items(): + req.add_header(key, value) + + try: + with urlopen(req) as response: + resp_bytes = response.read() + resp_str = resp_bytes.decode('utf-8') + resp_json = json.loads(resp_str) + return resp_json["data"]["endpoint"] + except Exception as e: + raise BrowserStackLocalError('Error trying to fetch the source url for downloading the binary: {}'.format(e)) + @staticmethod def set_version(version): LocalBinary._version = version From baa2960d44a292fe8015d229540d99947432d9e6 Mon Sep 17 00:00:00 2001 From: amaanbs Date: Tue, 10 Jun 2025 02:42:50 +0530 Subject: [PATCH 17/25] minor fixes --- browserstack/local.py | 5 +++-- browserstack/local_binary.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/browserstack/local.py b/browserstack/local.py index 544c791..3e3fa54 100644 --- a/browserstack/local.py +++ b/browserstack/local.py @@ -50,10 +50,11 @@ def get_package_version(self): return version def _generate_cmd(self): - cmd = [self.binary_path, '-d', 'start', '-logFile', self.local_logfile_path, "-k", self.key, '--source', 'python:' + self.get_package_version()] + cmd = [self.binary_path, '-d', 'start', '-logFile', self.local_logfile_path, "-k", self.key, '--source', 'python:' + self.get_package_version(), '--bs-host "k8s-devlocal.bsstag.com"'] for o in self.options.keys(): if self.options.get(o) is not None: cmd = cmd + self.__xstr(o, self.options.get(o)) + print(">>> CMD : ", cmd) return cmd def _generate_stop_cmd(self): @@ -101,7 +102,7 @@ def start(self, **kwargs): output_string = err.decode() data = json.loads(output_string) - + print(">>> DATA : ", data) if data['state'] != "connected": raise BrowserStackLocalError(data["message"]["message"]) else: diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 11c6ef0..14ca29c 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -89,7 +89,7 @@ def __available_dir(self): return final_path else: self.path_index += 1 - raise BrowserStackLocalError('Error trying to download BrowserStack Local binary') + raise BrowserStackLocalError('Error trying to download BrowserStack Local binary, exhausted user directories to download to.') def download(self, chunk_size=8192, progress_hook=None): headers = { @@ -108,6 +108,7 @@ def download(self, chunk_size=8192, progress_hook=None): total_size = int(response.info().get_all('Content-Length')[0].strip() or '0') bytes_so_far = 0 + # Limits retries to the number of directories dest_parent_dir = self.__available_dir() dest_binary_name = 'BrowserStackLocal' if self.is_windows: From 70f76a5cb09acdb07d97a0d41f74057a281a330c Mon Sep 17 00:00:00 2001 From: amaanbs Date: Tue, 10 Jun 2025 02:44:02 +0530 Subject: [PATCH 18/25] fixes --- browserstack/local.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browserstack/local.py b/browserstack/local.py index 3e3fa54..1ebb8e4 100644 --- a/browserstack/local.py +++ b/browserstack/local.py @@ -50,7 +50,7 @@ def get_package_version(self): return version def _generate_cmd(self): - cmd = [self.binary_path, '-d', 'start', '-logFile', self.local_logfile_path, "-k", self.key, '--source', 'python:' + self.get_package_version(), '--bs-host "k8s-devlocal.bsstag.com"'] + cmd = [self.binary_path, '-d', 'start', '-logFile', self.local_logfile_path, "-k", self.key, '--source', 'python:' + self.get_package_version()] for o in self.options.keys(): if self.options.get(o) is not None: cmd = cmd + self.__xstr(o, self.options.get(o)) @@ -102,7 +102,7 @@ def start(self, **kwargs): output_string = err.decode() data = json.loads(output_string) - print(">>> DATA : ", data) + if data['state'] != "connected": raise BrowserStackLocalError(data["message"]["message"]) else: From 2482e0e0d1d7c82a3e75b4c8011a626a181a5d63 Mon Sep 17 00:00:00 2001 From: amaanbs Date: Tue, 10 Jun 2025 02:45:21 +0530 Subject: [PATCH 19/25] fixes --- browserstack/local.py | 1 - 1 file changed, 1 deletion(-) diff --git a/browserstack/local.py b/browserstack/local.py index 1ebb8e4..544c791 100644 --- a/browserstack/local.py +++ b/browserstack/local.py @@ -54,7 +54,6 @@ def _generate_cmd(self): for o in self.options.keys(): if self.options.get(o) is not None: cmd = cmd + self.__xstr(o, self.options.get(o)) - print(">>> CMD : ", cmd) return cmd def _generate_stop_cmd(self): From f66802718d765a0fd24236b9264549e60ae75fcf Mon Sep 17 00:00:00 2001 From: amaanbs Date: Tue, 17 Jun 2025 20:01:23 +0530 Subject: [PATCH 20/25] Version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 83ea1ef..bdcd2b7 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.10', + version = '1.2.11', description = 'Python bindings for Browserstack Local', long_description=open('README.md').read(), long_description_content_type='text/markdown', From 05922ae2b49b03ac7ff9f7b847e26d404ba46c38 Mon Sep 17 00:00:00 2001 From: amaanbs Date: Wed, 18 Jun 2025 10:10:45 +0530 Subject: [PATCH 21/25] Add user agent --- browserstack/local_binary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index 14ca29c..ddae4ab 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -44,7 +44,8 @@ def fetch_source_url(self): url = "https://local.browserstack.com/binary/api/v1/endpoint" headers = { "Content-Type": "application/json", - "Accept": "application/json" + "Accept": "application/json", + 'User-Agent': '/'.join(('browserstack-local-python', LocalBinary._version)) } data = {"auth_token": self.key} From b271687e19c9e01f313e5051973de2fe86f07910 Mon Sep 17 00:00:00 2001 From: "Yash D. Saraf" Date: Thu, 19 Jun 2025 19:42:17 +0530 Subject: [PATCH 22/25] Bump version for release --- browserstack/local_binary.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index ddae4ab..fa65f34 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -45,7 +45,7 @@ def fetch_source_url(self): headers = { "Content-Type": "application/json", "Accept": "application/json", - 'User-Agent': '/'.join(('browserstack-local-python', LocalBinary._version)) + "User-Agent": '/'.join(('browserstack-local-python', LocalBinary._version)) } data = {"auth_token": self.key} diff --git a/setup.py b/setup.py index bdcd2b7..6b0617c 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.11', + version = '1.2.12', description = 'Python bindings for Browserstack Local', long_description=open('README.md').read(), long_description_content_type='text/markdown', From 1c15e470bf0139c3bbaafba0d53f24f4879f9a6b Mon Sep 17 00:00:00 2001 From: "Yash D. Saraf" Date: Tue, 3 Mar 2026 14:03:09 +0530 Subject: [PATCH 23/25] LOC-5083: Add support for Linux arm64 binary Detect arm64 architecture via platform.machine() == 'aarch64' and download BrowserStackLocal-linux-arm64 binary. This check is placed before the generic is_64bits check to avoid arm64 falling through to the x64 path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- browserstack/local_binary.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/browserstack/local_binary.py b/browserstack/local_binary.py index fa65f34..13144fa 100644 --- a/browserstack/local_binary.py +++ b/browserstack/local_binary.py @@ -24,6 +24,8 @@ def __init__(self, key, error_object=None): elif osname == 'Linux': if self.is_alpine(): self.http_path = source_url + "BrowserStackLocal-alpine" + elif platform.machine() == 'aarch64': + self.http_path = source_url + "BrowserStackLocal-linux-arm64" else: if is_64bits: self.http_path = source_url + "BrowserStackLocal-linux-x64" From 5afc4462c1607be14a7091a0df9d970fe54b68d3 Mon Sep 17 00:00:00 2001 From: "Yash D. Saraf" Date: Tue, 3 Mar 2026 14:15:39 +0530 Subject: [PATCH 24/25] 1.2.13 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6b0617c..77d13f9 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.12', + version = '1.2.13', description = 'Python bindings for Browserstack Local', long_description=open('README.md').read(), long_description_content_type='text/markdown', From 2172ec1f6fd11203f27e14b898db53d90b636ddb Mon Sep 17 00:00:00 2001 From: "Yash D. Saraf" Date: Tue, 3 Mar 2026 23:33:43 +0530 Subject: [PATCH 25/25] Fix python release --- requirements-release.txt | 3 +++ setup.cfg | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 requirements-release.txt diff --git a/requirements-release.txt b/requirements-release.txt new file mode 100644 index 0000000..6673e4d --- /dev/null +++ b/requirements-release.txt @@ -0,0 +1,3 @@ +build==1.4.0 +pkginfo==1.12.1.2 +twine==6.2.0 diff --git a/setup.cfg b/setup.cfg index 8c28267..7d0748b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -description-file = README.md +description_file = README.md diff --git a/setup.py b/setup.py index 77d13f9..7cf8cc6 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name = 'browserstack-local', packages = ['browserstack'], - version = '1.2.13', + version = '1.2.14', description = 'Python bindings for Browserstack Local', long_description=open('README.md').read(), long_description_content_type='text/markdown',