diff --git a/.gitignore b/.gitignore index a5a78b6..f97dc16 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,30 @@ -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -/.idea/ -/.DS_Store -src/.DS_Store -.DS_Store -/target/ -/AppTest.iml -/apache-maven-3.8.5/ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +/.idea/ +/.DS_Store +src/.DS_Store +.DS_Store +/target/ +/AppTest.iml +/apache-maven-3.8.5/ diff --git a/README.md b/README.md index 118b775..c59f0e5 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,58 @@ -# LT-appium-java-testng - -Sample repo to run app automation on real device on LambdaTest. - -**Below is the curl request to upload the app for automation** - -``` -curl --location --request POST 'https://manual-api.lambdatest.com/app/upload/realDevice' \ ---header 'Authorization: Basic ' \ ---form 'name="lambda1"' \ ---form 'appFile=@"/path/to/file"' -``` - -### **Step-1: Upload your application** - -```bash -curl -u "YOUR_LAMBDATEST_USERNAME":"YOUR_LAMBDATEST_ACCESS_KEY" \ ---location --request POST 'https://manual-api.lambdatest.com/app/upload/realDevice' \ ---form 'name="Android_App"' \ ---form 'appFile=@"/Users/macuser/Downloads/proverbial_android.apk"' -``` - -> **Note:** -> -> - If you do not have any **.apk** or **.ipa** file, you can run your sample tests on LambdaTest by using our sample [Android app](https://prod-mobile-artefacts.lambdatest.com/assets/docs/proverbial_android.apk) or sample [iOS app](https://prod-mobile-artefacts.lambdatest.com/assets/docs/proverbial_ios.ipa). -> - Response of above cURL will be a **JSON** object containing the `App URL` of the format - and will be used in the next step. - -### **Step 2: Write Your Automation Script** - -Write your automation script in the client language of your choice from the ones [supported by Appium](https://appium.io/downloads.html). In the sample automation script in Java for the sample app downloaded above. Ensure to update the `app_url`, `username` and `accesskey` in the below code. - -### **Step 3: Execute Your Test Case** - -Debug and run your code. Run iOSApp.java or AndroidApp.java in your editor. - -**Android:** - -``` -mvn test -P android-single -``` - -``` -mvn test -P android-parallel -``` - -**IOS:** - -``` -mvn test -P ios-single -``` - -``` -mvn test -P ios-parallel -``` - -### **Step 4: View Test Execution** - -Once you have run your tests, you can view the test execution along with logs. You will be able to see the test cases passing or failing. You can view the same at [LambdaTest Automation](https://accounts.lambdatest.com/login). +# LT-appium-java-testng + +Sample repo to run app automation on real device on LambdaTest. + +**Below is the curl request to upload the app for automation** + +``` +curl --location --request POST 'https://manual-api.lambdatest.com/app/upload/realDevice' \ +--header 'Authorization: Basic ' \ +--form 'name="lambda1"' \ +--form 'appFile=@"/path/to/file"' +``` + +### **Step-1: Upload your application** + +```bash +curl -u "YOUR_LAMBDATEST_USERNAME":"YOUR_LAMBDATEST_ACCESS_KEY" \ +--location --request POST 'https://manual-api.lambdatest.com/app/upload/realDevice' \ +--form 'name="Android_App"' \ +--form 'appFile=@"/Users/macuser/Downloads/proverbial_android.apk"' +``` + +> **Note:** +> +> - If you do not have any **.apk** or **.ipa** file, you can run your sample tests on LambdaTest by using our sample [Android app](https://prod-mobile-artefacts.lambdatest.com/assets/docs/proverbial_android.apk) or sample [iOS app](https://prod-mobile-artefacts.lambdatest.com/assets/docs/proverbial_ios.ipa). +> - Response of above cURL will be a **JSON** object containing the `App URL` of the format - and will be used in the next step. + +### **Step 2: Write Your Automation Script** + +Write your automation script in the client language of your choice from the ones [supported by Appium](https://appium.io/downloads.html). In the sample automation script in Java for the sample app downloaded above. Ensure to update the `app_url`, `username` and `accesskey` in the below code. + +### **Step 3: Execute Your Test Case** + +Debug and run your code. Run iOSApp.java or AndroidApp.java in your editor. + +**Android:** + +``` +mvn test -P android-single +``` + +``` +mvn test -P android-parallel +``` + +**IOS:** + +``` +mvn test -P ios-single +``` + +``` +mvn test -P ios-parallel +``` + +### **Step 4: View Test Execution** + +Once you have run your tests, you can view the test execution along with logs. You will be able to see the test cases passing or failing. You can view the same at [LambdaTest Automation](https://accounts.lambdatest.com/login). diff --git a/pom.xml b/pom.xml index 7e3be79..ea9f7bb 100644 --- a/pom.xml +++ b/pom.xml @@ -1,154 +1,176 @@ - - - 4.0.0 - - org.example - AppTest - 1.0-SNAPSHOT - - - 8 - 8 - - - - - - org.seleniumhq.selenium - selenium-java - 3.141.59 - - - - - io.appium - java-client - 7.6.0 - - - - org.testng - testng - 7.4.0 - test - - - - com.mashape.unirest - unirest-java - 1.4.9 - - - - org.webjars.npm - xmlhttprequest-ssl - 2.0.0 - - - - - org.json - json - 20211205 - - - org.testng - testng - 7.4.0 - compile - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M5 - - - true - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - - - - android-single - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/java/android-single.xml - - - - - - - - - android-parallel - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/java/android-parallel.xml - - - - - - - - - ios-single - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/java/ios-single.xml - - - - - - - - ios-parallel - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/java/ios-parallel.xml - - - - - - - - - \ No newline at end of file + + + + 4.0.0 + + org.example + AppTest + 1.0-SNAPSHOT + + + UTF-8 + 22 + ${java.version} + ${java.version} + + + + + org.seleniumhq.selenium + selenium-java + 4.27.0 + + + + io.appium + java-client + 9.3.0 + + + + org.testng + testng + 7.10.2 + compile + + + + com.konghq + unirest-java + 3.14.5 + + + + org.json + json + 20240303 + + + + org.webjars.npm + xmlhttprequest-ssl + 2.1.2 + + + + org.slf4j + slf4j-simple + 2.0.13 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + true + + + + + + + + + android-single + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/java/android-single.xml + + + + + + + + + android-app-profiling + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/java/android-app-profiling.xml + + + + + + + + + android-parallel + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/java/android-parallel.xml + + + + + + + + + ios-single + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/java/ios-single.xml + + + + + + + + + ios-parallel + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/java/ios-parallel.xml + + + + + + + + + diff --git a/src/main/java/AndroidApp.java b/src/main/java/AndroidApp.java deleted file mode 100644 index 8d940d6..0000000 --- a/src/main/java/AndroidApp.java +++ /dev/null @@ -1,98 +0,0 @@ -import io.appium.java_client.AppiumDriver; -import io.appium.java_client.MobileBy; -import io.appium.java_client.MobileElement; -import io.appium.java_client.android.AndroidElement; -import org.openqa.selenium.remote.DesiredCapabilities; -import org.openqa.selenium.support.ui.ExpectedConditions; -import org.openqa.selenium.support.ui.WebDriverWait; -import org.testng.annotations.Test; - -import java.net.URL; -import java.util.List; - -public class AndroidApp { - - String userName = System.getenv("LT_USERNAME") == null ? "username" : System.getenv("LT_USERNAME"); //Add username here - String accessKey = System.getenv("LT_ACCESS_KEY") == null ? "accessKey" : System.getenv("LT_ACCESS_KEY"); //Add accessKey here - String app_id = System.getenv("LT_APP_ID") == null ? "lt://proverbial-android" : System.getenv("LT_APP_ID"); //Enter your LambdaTest App ID at the place of lt://proverbial-android - String grid_url = System.getenv("LT_GRID_URL") == null ? "mobile-hub.lambdatest.com" : System.getenv("LT_GRID_URL"); - - AppiumDriver driver; - - @Test - @org.testng.annotations.Parameters(value = {"device", "version", "platform"}) - public void AndroidApp1(String device, String version, String platform) { - try { - DesiredCapabilities capabilities = new DesiredCapabilities(); - capabilities.setCapability("build", "Java TestNG"); - capabilities.setCapability("name", platform + " " + device + " " + version); - capabilities.setCapability("deviceName", device); - capabilities.setCapability("platformVersion", version); - capabilities.setCapability("platformName", platform); - capabilities.setCapability("isRealMobile", true); - //AppURL (Create from Wikipedia.apk sample in project) - capabilities.setCapability("app", app_id); //Enter your app url - capabilities.setCapability("deviceOrientation", "PORTRAIT"); - capabilities.setCapability("network", false); - capabilities.setCapability("visual", true); - capabilities.setCapability("devicelog", true); - capabilities.setCapability("autoGrantPermissions", true); - - //capabilities.setCapability("geoLocation", "HK"); - - String hub = "https://" + userName + ":" + accessKey + "@" + grid_url + "/wd/hub"; - driver = new AppiumDriver(new URL(hub), capabilities); - - MobileElement color = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/color"); - //Changes color to pink - color.click(); - Thread.sleep(1000); - //Back to orginal color - color.click(); - - MobileElement text = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/Text"); - //Changes the text to "Proverbial" - text.click(); - - //toast will be visible - MobileElement toast = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/toast"); - toast.click(); - - //notification will be visible - MobileElement notification = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/notification"); - notification.click(); - Thread.sleep(2000); - - //Opens the geolocation page - MobileElement geo = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/geoLocation"); - geo.click(); - Thread.sleep(5000); - - //takes back to home page - MobileElement home = (MobileElement) driver.findElementByAccessibilityId("Home"); - home.click(); - - //Takes to speed test page - MobileElement speedtest = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/speedTest"); - speedtest.click(); - Thread.sleep(5000); - - MobileElement Home = (MobileElement) driver.findElementByAccessibilityId("Home"); - Home.click(); - - //Opens the browser - MobileElement browser = (MobileElement) driver.findElementByAccessibilityId("Browser"); - browser.click(); - - MobileElement url = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/url"); - url.sendKeys("https://www.lambdatest.com"); - - MobileElement find = (MobileElement) driver.findElementById("com.lambdatest.proverbial:id/find"); - find.click(); - driver.quit(); - } catch (Exception e) { - e.printStackTrace(); - } - - } -} diff --git a/src/test/java/AndroidApp.java b/src/test/java/AndroidApp.java new file mode 100644 index 0000000..8f1f9cf --- /dev/null +++ b/src/test/java/AndroidApp.java @@ -0,0 +1,105 @@ +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.AppiumBy; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.testng.annotations.Test; + +import java.net.URL; +import java.time.Duration; +import java.util.HashMap; + +public class AndroidApp { + + String userName = System.getenv("LT_USERNAME") == null ? "username" : System.getenv("LT_USERNAME"); + String accessKey = System.getenv("LT_ACCESS_KEY") == null ? "accessKey" : System.getenv("LT_ACCESS_KEY"); + String app_id = System.getenv("LT_APP_ID") == null ? "lt://proverbial-android" : System.getenv("LT_APP_ID"); + String grid_url = System.getenv("LT_GRID_URL") == null ? "mobile-hub.lambdatest.com" : System.getenv("LT_GRID_URL"); + + AppiumDriver driver; + + @Test + @org.testng.annotations.Parameters(value = {"device", "version", "platform"}) + public void AndroidApp1(String device, String version, String platform) { + try { + DesiredCapabilities options = new DesiredCapabilities(); + HashMap ltOptions = new HashMap<>(); + ltOptions.put("w3c", true); + ltOptions.put("isRealMobile", true); + ltOptions.put("build", "Java TestNG Build"); + ltOptions.put("name", "Android Test"); + ltOptions.put("visual", true); + ltOptions.put("devicelog", true); + ltOptions.put("autoGrantPermissions", true); + ltOptions.put("platformName", "android"); + ltOptions.put("platformVersion", "15"); + ltOptions.put("deviceName", "Galaxy.*"); + ltOptions.put("app", ",{app_id}"); + options.setCapability("lt:options", ltOptions); + + + String hub = "https://" + userName + ":" + accessKey + "@" + grid_url + "/wd/hub"; + + System.out.println("USERNAME: " + System.getenv("LT_USERNAME")); + System.out.println("ACCESS_KEY: " + System.getenv("LT_ACCESS_KEY")); + + driver = new AppiumDriver(new URL(hub), options); + + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); + + // Changes color to pink + WebElement color = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/color"))); + color.click(); + Thread.sleep(1000); + color.click(); + + // Changes the text to "Proverbial" + WebElement text = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/Text"))); + text.click(); + + // Toast will be visible + WebElement toast = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/toast"))); + toast.click(); + + // Notification will be visible + WebElement notification = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/notification"))); + notification.click(); + Thread.sleep(2000); + + // Opens the geolocation page + WebElement geo = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/geoLocation"))); + geo.click(); + Thread.sleep(5000); + + // Takes back to home page + WebElement home = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("Home"))); + home.click(); + + // Takes to speed test page + WebElement speedtest = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/speedTest"))); + speedtest.click(); + Thread.sleep(5000); + + // Back to home page + WebElement home2 = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("Home"))); + home2.click(); + + // Opens the browser + WebElement browser = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("Browser"))); + browser.click(); + + WebElement url = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/url"))); + url.click(); + url.sendKeys("https://www.lambdatest.com"); + + WebElement find = wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.id("com.lambdatest.proverbial:id/find"))); + find.click(); + + driver.quit(); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/test/java/android-parallel.xml b/src/test/java/android-parallel.xml index 5d21678..9044c4d 100644 --- a/src/test/java/android-parallel.xml +++ b/src/test/java/android-parallel.xml @@ -1,22 +1,33 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/android-single.xml b/src/test/java/android-single.xml index fe5d38c..919c1db 100644 --- a/src/test/java/android-single.xml +++ b/src/test/java/android-single.xml @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/iOSApp.java b/src/test/java/iOSApp.java similarity index 54% rename from src/main/java/iOSApp.java rename to src/test/java/iOSApp.java index bd28a71..c184534 100644 --- a/src/main/java/iOSApp.java +++ b/src/test/java/iOSApp.java @@ -1,15 +1,14 @@ import io.appium.java_client.AppiumDriver; -import io.appium.java_client.MobileBy; -import io.appium.java_client.MobileElement; -import io.appium.java_client.android.AndroidElement; +import io.appium.java_client.AppiumBy; +import io.appium.java_client.android.options.UiAutomator2Options; +import io.appium.java_client.ios.IOSDriver; +import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; -import org.openqa.selenium.JavascriptExecutor; -import org.openqa.selenium.remote.DesiredCapabilities; - import java.net.URL; - +import java.time.Duration; +import java.util.HashMap; import org.testng.annotations.Test; public class iOSApp { @@ -26,59 +25,72 @@ public class iOSApp { public void iOSApp1(String device, String version, String platform) { try { - DesiredCapabilities capabilities = new DesiredCapabilities(); - capabilities.setCapability("build", "Java TestNG"); - capabilities.setCapability("name", platform + " " + device + " " + version); - capabilities.setCapability("deviceName", device); - capabilities.setCapability("platformVersion", version); - capabilities.setCapability("platformName", platform); - capabilities.setCapability("isRealMobile", true); - capabilities.setCapability("app", app_id); //Enter your app url - capabilities.setCapability("network", false); - capabilities.setCapability("visual", true); - capabilities.setCapability("devicelog", true); - //capabilities.setCapability("geoLocation", "HK"); - - String hub = "https://" + userName + ":" + accessKey + "@" + grid_url + "/wd/hub"; - driver = new AppiumDriver(new URL(hub), capabilities); - - WebDriverWait Wait = new WebDriverWait(driver, 30); +// UiAutomator2Options options = new UiAutomator2Options(); +// + + // LambdaTest Specific Options + DesiredCapabilities options = new DesiredCapabilities(); + HashMap ltOptions = new HashMap<>(); + ltOptions.put("w3c", true); + ltOptions.put("isRealMobile", true); + ltOptions.put("build", "Java TestNG iPad Build"); + ltOptions.put("name", "iOS iPad Test"); + ltOptions.put("visual", true); + ltOptions.put("devicelog", true); + ltOptions.put("platformName", "ios"); + ltOptions.put("platformVersion", "17"); + ltOptions.put("deviceName", "iPhone.*"); + ltOptions.put("app", ""); + options.setCapability("lt:options", ltOptions); + + String hub = "https://" + userName + ":" + accessKey + "@mobile-hub.lambdatest.com/wd/hub"; + + System.out.println("USERNAME: " + System.getenv("LT_USERNAME")); + System.out.println("ACCESS_KEY: " + System.getenv("LT_ACCESS_KEY")); + + driver = new IOSDriver(new URL(hub), options); + + + WebDriverWait Wait = new WebDriverWait(driver, Duration.ofSeconds(30)); //Changes the color of the text - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("color"))).click(); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("color"))).click(); Thread.sleep(1000); //Changes the text to "Proverbial" - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("Text"))).click(); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("Text"))).click(); Thread.sleep(1000); //Toast will be visible - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("toast"))).click(); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("toast"))).click(); Thread.sleep(1000); //Notification will be visible - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("notification"))).click(); - Thread.sleep(4000); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("notification"))).click(); + Thread.sleep(8000); //Opens the geolocation page - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("geoLocation"))).click(); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("geoLocation"))).click(); Thread.sleep(4000); //Takes back driver.navigate().back(); //Takes to speedtest page - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("speedTest"))).click(); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("speedTest"))).click(); Thread.sleep(4000); driver.navigate().back(); //Opens the browser - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("Browser"))).click(); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("Browser"))).click(); Thread.sleep(1000); - MobileElement url = (MobileElement) driver.findElementByAccessibilityId("url"); + + WebElement url = Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("url"))); + Thread.sleep(1000); + url.click(); url.sendKeys("https://www.lambdatest.com"); - Wait.until(ExpectedConditions.presenceOfElementLocated(MobileBy.AccessibilityId("find"))).click(); + Wait.until(ExpectedConditions.presenceOfElementLocated(AppiumBy.accessibilityId("find"))).click(); Thread.sleep(1000); driver.quit(); } catch (Exception e) { @@ -86,4 +98,4 @@ public void iOSApp1(String device, String version, String platform) { } } -} +} \ No newline at end of file diff --git a/src/test/java/ios-parallel.xml b/src/test/java/ios-parallel.xml index 9a66b1b..95125e1 100644 --- a/src/test/java/ios-parallel.xml +++ b/src/test/java/ios-parallel.xml @@ -1,22 +1,22 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ios-single.xml b/src/test/java/ios-single.xml index 75a4e25..77608df 100644 --- a/src/test/java/ios-single.xml +++ b/src/test/java/ios-single.xml @@ -1,14 +1,14 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file