From 0fadae66c2c2692099b8b824e6244f900e81e469 Mon Sep 17 00:00:00 2001
From: Aditya Samantaray <aditya.sam@browserstack.com>
Date: Wed, 12 Jul 2023 20:03:34 +0530
Subject: [PATCH] chore: add patch capabilities for playwright mobile devices

---
 playwright-test/fixtures.js          | 63 ++++++++++++++++++++++++----
 playwright-test/package.json         |  6 +--
 playwright-test/playwright.config.js |  7 ++++
 3 files changed, 65 insertions(+), 11 deletions(-)

diff --git a/playwright-test/fixtures.js b/playwright-test/fixtures.js
index 9576eb6..58d130d 100644
--- a/playwright-test/fixtures.js
+++ b/playwright-test/fixtures.js
@@ -21,6 +21,19 @@ const caps = {
   'client.playwrightVersion': clientPlaywrightVersion,
 };
 
+// BrowserStack Specific Capabilities.
+const device_caps = {
+  osVersion: "13.0",
+  deviceName: "Samsung Galaxy S23", // "Samsung Galaxy S22 Ultra", "Google Pixel 7 Pro", "OnePlus 9", etc.
+  browserName: "chrome",
+  realMobile: "true",
+  name: "My android playwright test",
+  build: "playwright-build-1",
+  'browserstack.username': process.env.BROWSERSTACK_USERNAME || 'YOUR_USERNAME',
+  'browserstack.accessKey':
+    process.env.BROWSERSTACK_ACCESS_KEY || 'YOUR_ACCESS_KEY',
+};
+
 exports.bsLocal = new BrowserStackLocal.Local();
 
 // replace YOUR_ACCESS_KEY with your key. You can also set an environment variable - "BROWSERSTACK_ACCESS_KEY".
@@ -43,6 +56,20 @@ const patchCaps = (name, title) => {
   caps.name = title;
 };
 
+const patchMobileCaps = (name, title) => {
+  let combination = name.split(/@browserstack/)[0];
+  let [browerCaps, osCaps] = combination.split(/:/);
+  let [browser, deviceName] = browerCaps.split(/@/);
+  let osCapsSplit = osCaps.split(/ /);
+  let os = osCapsSplit.shift();
+  let osVersion = osCapsSplit.join(" ");
+  caps.browser = browser ? browser : "chrome";
+  caps.deviceName = deviceName ? deviceName : "Samsung Galaxy S22 Ultra";
+  caps.osVersion = osVersion ? osVersion : "12.0";
+  caps.name = title;
+  caps.realMobile = "true";
+};
+
 const isHash = (entity) => Boolean(entity && typeof(entity) === "object" && !Array.isArray(entity));
 const nestedKeyValue = (hash, keys) => keys.reduce((hash, key) => (isHash(hash) ? hash[key] : undefined), hash);
 const isUndefined = val => (val === undefined || val === null || val === '');
@@ -63,13 +90,29 @@ exports.test = base.test.extend({
   page: async ({ page, playwright }, use, testInfo) => {
     // Use BrowserStack Launched Browser according to capabilities for cross-browser testing.
     if (testInfo.project.name.match(/browserstack/)) {
-      patchCaps(testInfo.project.name, `${testInfo.title}`);
-      const vBrowser = await playwright.chromium.connect({
-        wsEndpoint:
-          `wss://cdp.browserstack.com/playwright?caps=` +
-          `${encodeURIComponent(JSON.stringify(caps))}`,
-      });
-      const vContext = await vBrowser.newContext(testInfo.project.use);
+      let vBrowser, vContext, vDevice;
+      const isMobile = testInfo.project.name.match(/browserstack-mobile/);
+      if(isMobile) {
+        patchMobileCaps(
+          testInfo.project.name,
+          `${testInfo.file} - ${testInfo.title}`
+        );
+        vDevice = await playwright._android.connect(
+          `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(
+            JSON.stringify(device_caps)
+          )}`
+        );
+        await vDevice.shell("am force-stop com.android.chrome");
+        vContext = await vDevice.launchBrowser();
+      } else {
+        patchCaps(testInfo.project.name, `${testInfo.title}`);
+        vBrowser = await playwright.chromium.connect({
+          wsEndpoint:
+            `wss://cdp.browserstack.com/playwright?caps=` +
+            `${encodeURIComponent(JSON.stringify(caps))}`,
+        });
+        vContext = await vBrowser.newContext(testInfo.project.use);
+      } 
       const vPage = await vContext.newPage();
       await use(vPage);
       const testResult = {
@@ -82,7 +125,11 @@ exports.test = base.test.extend({
       await vPage.evaluate(() => {},
       `browserstack_executor: ${JSON.stringify(testResult)}`);
       await vPage.close();
-      await vBrowser.close();
+      if (isMobile) {
+        await vDevice.close();
+      } else {
+        await vBrowser.close();
+      }
     } else {
       use(page);
     }
diff --git a/playwright-test/package.json b/playwright-test/package.json
index 5311a86..eff6195 100644
--- a/playwright-test/package.json
+++ b/playwright-test/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "@playwright/test",
+  "name": "browserstack-playwright-sample",
   "version": "1.0.0",
   "author": "Karan Shah",
   "engines": {
@@ -11,8 +11,8 @@
     "test:local": "BROWSERSTACK_LOCAL=true npx playwright test --config=./playwright-local.config.js"
   },
   "devDependencies": {
-    "playwright": "~1.17.2",
-    "@playwright/test": "~1.17.2",
+    "playwright": "~1.34.1",
+    "@playwright/test": "~1.34.1",
     "browserstack-local": "^1.4.8"
   }
 }
diff --git a/playwright-test/playwright.config.js b/playwright-test/playwright.config.js
index 6597d9c..aa87f80 100644
--- a/playwright-test/playwright.config.js
+++ b/playwright-test/playwright.config.js
@@ -48,6 +48,13 @@ const config = {
         // ...devices['iPhone 12 Pro Max'],
       },
     },
+    {
+      name: 'chrome@Samsung Galaxy S22:13@browserstack-mobile',
+      use: {
+        browserName: 'chromium',
+        channel: 'chrome'
+      },
+    },
     // -- Local Projects --
 
     // Test against playwright browsers