appium / java-client

Java language binding for writing Appium Tests, conforms to W3C WebDriver Protocol
Apache License 2.0
1.2k stars 755 forks source link

No such session exception showing up #1628

Open skhandekar1 opened 2 years ago

skhandekar1 commented 2 years ago

The problem

I am trying to run a test class in a suite. The class that is giving the error is running a data driven test. The beforeMethod in the test runs fine, but for some reason the test fails as soon as it enters the @Test and tries setting a parameter in my Android Mobile test. This problem is consistent and only happens when i run the entire suite of classes. If I run the above mentioned class individually, it runs fine.

Environment

Details

Error is as follows:

[ERROR] 2022-01-08 09:54:28 framework.Listeners.ExecutionListener 160 onTestFailure - Error type : 
**org.openqa.selenium.NoSuchSessionException: Session ID is null. Using WebDriver after calling quit()?**
Build info: version: '3.141.59', revision: 'e82be7d358', time: '2018-11-14T08:17:03'
System info: host: 'N-PF2JP03M', ip: '172.22.64.1', os.name: 'Windows 10', os.arch: 'amd64', os.version: '10.0', java.version: '1.8.0_281'
Driver info: driver.version: AppiumDriver
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:125) ~[selenium-remote-driver-3.141.59.jar:?]
    at io.appium.java_client.remote.AppiumCommandExecutor.execute(AppiumCommandExecutor.java:231) ~[java-client-6.1.0.jar:?]
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:552) ~[selenium-remote-driver-3.141.59.jar:?]
    at io.appium.java_client.DefaultGenericMobileDriver.execute(DefaultGenericMobileDriver.java:46) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.AppiumDriver.execute(AppiumDriver.java:1) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.HasSessionDetails.getSessionDetails(HasSessionDetails.java:39) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.HasSessionDetails.getSessionDetail(HasSessionDetails.java:56) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.HasSessionDetails.isBrowser(HasSessionDetails.java:86) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.AppiumDriver.isBrowser(AppiumDriver.java:271) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.lambda$0(WebDriverUnpackUtility.java:87) ~[java-client-6.1.0.jar:?]
    at java.util.Optional.map(Unknown Source) ~[?:1.8.0_281]
    at io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType(WebDriverUnpackUtility.java:83) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.pagefactory.AppiumElementLocator.getBy(AppiumElementLocator.java:90) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.pagefactory.AppiumElementLocator.findElement(AppiumElementLocator.java:117) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.pagefactory.interceptors.InterceptorOfASingleElement.intercept(InterceptorOfASingleElement.java:60) ~[java-client-6.1.0.jar:?]
    at io.appium.java_client.android.AndroidElement$$EnhancerByCGLIB$$b598166c.clear(<generated>) ~[java-client-6.1.0.jar:?]
    at framework.Screens.Patient.Sessions.CommonComponents.Area.AmplitudeControls.setAmplitude(AmplitudeControls.java:42) ~[test-classes/:?]
    at framework.commonUtilities.DataProvider.SetupData.setParameters(SetupData.java:32) ~[test-classes/:?]
    at com.tp.tp397_v2.Procedure4.TP397_Procedure4_Section08.execute_SNSConfiguredLeadProgram(TP397_Procedure4_Section08.java:160) ~[test-classes/:?]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_281]
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_281]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_281]
    at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_281]

I tried printing out the session ID i get from my driver at various places to find where its getting set to null, but i am not seeing it set to null anywhere:

    @BeforeMethod
    public void BeforeMethod(Method method, Object[] testData){
        //Set testName for each testcase
        log.info("\n******************************************************\n"
                + "BeforeMethod called from TP397_Procedure4_Section08\n******************************************************\n");

        for(String str : testData[0].toString().split(","))
        {
            if(str.contains("TestID=")) {
                testName.set(method.getName() + "_" + str.split("=")[1]);
                break;
            }
        }

        //If CP is on login screen in case of a test failure OR for the very first execution
        //Login to CP, search and select patient Rosa.
        if(cpApp.Login.isVisible(cpApp.Login.clinicLogo, "txtClinicName")) {
            cpApp.Login.Login(cpAppProps.Passcode);
            cpApp.MyPatients.Search(patient.PatientName);
            cpApp.MyPatients.EnterFirstPatient();

            //From Sessions tab, create an OR session
            cpApp.Patient.TapSessions();
        }
        //Successive executions: CP is on OR Session screen.
        else {
            try{
                //For valid stimulation scenarios
                if(ORScreen.isStimulationActive()) { }
            }catch(Exception e) { }

            ORScreen.TapDone();
        }
        cpApp.Patient.waitForVisibilityOf(cpApp.Patient.sessionsTab, "sessionsTab");
        cpApp.Patient.Sessions.AddSession();
        cpApp.Patient.waitForVisibilityOf(cpApp.Patient.Sessions.ORSessionButton, "btnORSession");
        cpApp.Patient.Sessions.ChooseORSession();
        ORScreen.isDisplayed(patient.ClinicalIndication);
System.out.println("Session id in execute_SNSConfiguredLeadProgram @BeforeMethod is: " + driver.getSessionId());
    }

    @SuppressWarnings("unlikely-arg-type")
    @FrameworkAnnotation(author = { "RShah" }, deviceType="ETM1")
    @Test (description="Data-driven Test for SNS Configured Lead Program", dataProvider="stimulationTestData", dataProviderClass=DataProviders.class)
    public void execute_SNSConfiguredLeadProgram(Map<String, String> data) {    
        //Test ID which are covered in this test
        //TP397-025
        //TP397-026
        //TP397-027
        //TP397-028
        //TP397-029
        //TP397-030
        //TP397-031
        //TP397-032
        //TP397-033
System.out.println("Session id in execute_SNSConfiguredLeadProgram @Test (1) is: " + driver.getSessionId());
            log.info("The parameters used for this test are: \n" + data);
            ExtentLogger.logInfo("The parameters used for this test are: <br>" + data);
System.out.println("Session id in execute_SNSConfiguredLeadProgram @Test (2) is: " + driver.getSessionId());            
            ResultWriterParams.testCaseId = data.get("TestID");
            ResultWriterParams.acceptanceCriteria = String.format("Test CP Log with following parameters %s", programParameters);
            NTestCase testID = new NTestCase(ResultWriterParams.testCaseId, TP_ID);
System.out.println("Session id in execute_SNSConfiguredLeadProgram @Test (3) is: " + driver.getSessionId());
            if(data.get("TestType").toLowerCase().matches("configuredlead_valid")) {
                //Set OR Session parameters according to datasheet
                ExtentLogger.logInfo("Set OR Session parameters according to datasheet");
System.out.println("Session id in execute_SNSConfiguredLeadProgram @Test (4) is: " + driver.getSessionId());
                SetupData.setParameters(DataProviderType.OR_SESSION_DATA, data);

Point it fails at is SetupData.setParameters(DataProviderType.OR_SESSION_DATA, data);

The relevant portion where failure occurs in my SetupData class is as follows:


public class SetupData {
    private static AreaParametersComponent AreaParameters = new AreaParametersComponent();
    private static ORSessionScreen ORScreen= new ORSessionScreen();

    //From sessionType figure out the workbook to look at for test data
    static Map<String, String> WorkBookName = new HashMap<String, String>() {{
              put("TP397_ORSession", TestDataConstants.OR_STIMULATION_TESTDATA_FILE);
              put("TP368_programmingSession", TestDataConstants.STIMULATION_TEST_DATA_FILE);        
    }};

    //Setup data 
    public static void setParameters(DataProviderType dataType, Map<String, String> data) {
        switch(dataType) {
            case PATIENT_DATA:
                //Do patient data related setup
            case OR_SESSION_DATA:
                //Method to set OR Session parameters according to data from Stimulation dataSheets
                //Data is received as a HashMap argument
                AreaParameters.Amplitude.setAmplitude(data.get(TestData.ORSessionData.OR_Amplitude));
                AreaParameters.PulseWidth.setPulseWidth(data.get(TestData.ORSessionData.OR_PulseWidth));
                AreaParameters.Rate.setRate(data.get(TestData.ORSessionData.OR_ETX_Rate));
                setElectrodes(data);

            case PROGRAMMING_SESSION_DATA:
                //Do programming session related setup
        }
    }

My Dataproviders class is as follows:

public class DataProviders {
    private Logger log = LogManager.getLogger(this.getClass());
    private static String worksheet;
    private static String testType_String;
    private static String Tp_Id;

    public static void setTestParameters(String workSheetName, String testTypeString, String TP_ID) {
        worksheet = workSheetName;
        testType_String = testTypeString;
        Tp_Id = TP_ID;
    }

    /*
     * DataProvider which converts the excel data to a hashmap and returns hashmap as a two-dimensional object array
     * that @Test tests can use to iterate over
     */
    @DataProvider
    public static Object[][] stimulationTestData(Method method) {
        List<String> testIDs;
        int iTestCaseRow;

        //Create new results arraylist
        List<Map<String, String>> results = new ArrayList<>();

        try {
            ExcelUtils.setExcelFile(SetupData.WorkBookName.get(Tp_Id), worksheet);

            // Fetching the TestCase ID's related to particular TestType
            testIDs = ExcelUtils.getAllTestIDListForTestType(testType_String, 1, 2);
            System.out.println(testIDs);

            //Only get rows that belong to the testID's
            for(String testID:testIDs) {
                // Getting the Test Case row which contains the testID
                try {
                    iTestCaseRow = ExcelUtils.getRowContains(testID,2);

                    //Get keys from header row (Row 1) and 
                    List<String> keys = ExcelUtils.getValuesInSpecificRow(1);           
                    List<String> values = ExcelUtils.getValuesInSpecificRow(iTestCaseRow);

                    results.add(ExcelUtils.transform(keys, values));
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }                           
            }
        } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        return ExcelUtils.asTwoDimensionalArray(results);   
    }   
}

My Base class is as follows (Renamed for security reasons):

@Listeners({ExecutionListener.class, ExtentListener.class})
public abstract class BaseClass implements ITest{

    protected static ResultWriterCSV resultWriter;
    private static Runtime runTime = Runtime.getRuntime();
    private static Boolean setupComplete = false;

    protected static AppiumDriver<MobileElement> driver;
    private AppiumDriverLocalService service;

    public static ClinicianProgrammer cpApp;
    public static TabletIO tablet;
    private static DesiredCapabilities caps;
    protected Logger log = LogManager.getLogger(this.getClass());       
    protected ThreadLocal<String> testName = new ThreadLocal<>();
    private LoadPropertiesFile loadPropFile;
    private static File appium_logfile_folder;

    @BeforeSuite
    public void timestamp_AppiumLogs_Folder(ITestContext ctx) {
        //If appium_logfile_folder exists from previous run, timestamp it
        appium_logfile_folder = new File(TestDataConstants.APPIUM_LOGFILE_LOCATION + "/" + ctx.getSuite().getXmlSuite().getName());
        String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm'.csv'").format(new Date());

        if(appium_logfile_folder.exists()) {
            appium_logfile_folder.renameTo(new File(appium_logfile_folder + "-" + fileName));
            appium_logfile_folder.delete();
        }
    }

    @BeforeTest
    public void setup(ITestContext ctx) {   
        setupComplete = false;
        resultWriter = ResultWriterCSV.getInstance();

        try {
            runTime.exec("adb shell am force-stop 'CP.Android'");

        } catch (IOException e) {
            e.printStackTrace();
        }

        //Create output folder within the outputs/AppiumLogs folder with TP name and procedure name 
        if(!appium_logfile_folder.exists()) {
            appium_logfile_folder.mkdir();
        }
    }

    @Parameters({"deviceName","platformVersion", "portNumber"})
    @BeforeClass
    public void initialize( String deviceName, String platformVersion, String portNumber) throws MalformedURLException, WebDriverException{
        try {
                log.info("BeforeClass from BaseClass");
                //Start Appium Service
                if(checkIfServiceIsRunning(Integer.parseInt(portNumber)))
                    stopAppiumService();

                try {
                    startAppiumService(portNumber, this.getClass().getSimpleName());
                } catch (Exception e) {
                    e.printStackTrace();
                }

                //Set Appium driver capabilities
                loadPropFile = new LoadPropertiesFile(System.getProperty("user.dir")  + "\\framework.properties");
                loadPropFile.loadPropertiesFile();
                caps = new DesiredCapabilities();

                caps.setCapability("platformName", loadPropFile.getProperty("appium.platformName"));
                caps.setCapability("newCommandTimeout", loadPropFile.getProperty("appium.newCommandTimeout"));
                caps.setCapability("resetKeyboard", loadPropFile.getProperty("appium.resetKeyboard"));
                caps.setCapability("autoGrantPermissions", loadPropFile.getProperty("appium.autoGrantPermissions"));
                caps.setCapability("disableAndroidWatchers", true);
                caps.setCapability("skipUnlock", true);
                caps.setCapability(MobileCapabilityType.PLATFORM_VERSION, platformVersion);
                caps.setCapability(MobileCapabilityType.UDID, deviceName);
                caps.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, loadPropFile.getProperty("appium.appPackage"));
                caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, loadPropFile.getProperty("appium.automationName"));
                caps.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, loadPropFile.getProperty("appium.appActivity"));   
                caps.setCapability(MobileCapabilityType.NO_RESET, loadPropFile.getProperty("appium.dontStopAppOnReset"));   

                log.info("Starting Appium driver...");

                //Start Appium driver
                driver = new AppiumDriver<MobileElement>(new URL("http://127.0.0.1:" + portNumber + "/wd/hub"), caps);
System.out.println("Session id in BaseClass @BeforeClass is: " + driver.getSessionId());
                log.info("Appium driver started...");
                cpApp = new ClinicianProgrammer();
                tablet = new TabletIO();
        } catch (WebDriverException e) {            
            try {
                log.info("Retrying Appium driver start...");
                driver = new AppiumDriver<MobileElement>(new URL("http://127.0.0.1:" + portNumber + "/wd/hub"), caps);
            }catch(WebDriverException we) {
                log.error("Error starting Appium driver..." + we);
                we.printStackTrace();
                throw new WebDriverException();
            }
        } catch (MalformedURLException e) {     
            log.error("Error starting Appium driver..." + e.getMessage());
            log.error("Error starting Appium service...: full stack trace follows:", e);
            e.printStackTrace();
            throw new MalformedURLException();
        }
    }

    public static WebDriver getDriver() {
        return driver;
    }

    /*Here alwaysRun is set to true because in normal circumstances BeforeMethod is skipped if BeforeClass fails
      we want to set the TestName to be set even if the BeforeClass fails. Setting alwaysRun to true ensures
      that the testName will always be set. This prevents bug in extentListener which was taking the previous testcase Name
      instead of the current one while logging in Extent Reports
      */
    @BeforeMethod(alwaysRun=true)
    public void setUpTest(Method method, Object[] testData, ITestContext ctx) { 
        log.info("BeforeMethod from BaseClass");
        try {
            Map<String,String> map = new HashMap<String,String>();

             //Updates names of testcases that use DataProviders
            if (testData.length > 0) {
                 //Code used for dataproviders from excel sheets that contain TestID
                String[] entries = testData[0].toString().split(",");
                if(entries.length>1) {
                    for(String entry:entries) {
                        map.put(entry.split("=")[0].trim(), entry.split("=")[1].trim());
                    }
                testName.set(method.getName() + "_" + map.get("TestID"));
                ctx.setAttribute("testName", method.getName() + "_" + map.get("TestID"));
                }
                else {
                    //Code used for dataproviders that have single entry and no TestID's, viz create_patient
                    testName.set(method.getName() + "_" + testData[0].toString());
                    ctx.setAttribute("testName", method.getName() + "_" + testData[0].toString());
                }
             } else {
                 testName.set(method.getName());
                 ctx.setAttribute("testName", method.getName());
             }

             log.info("Testname in BaseClass is: " + ctx.getAttribute("testName"));

            //Also check if app is already running. Helpful in conditions where app shutsdown in middle of any testcase though setup was completed successfully
            if (!setupComplete && driver!=null){
                log.info("Relaunching app due to setup incomplete or driver==null...");
                try {
                    driver.launchApp();
                    beforeEachTestMethod();
                }catch(Exception e) {
                    e.printStackTrace();
                    throw new Exception();
                }
            }
            setupComplete=true;
        }catch(Exception e) {e.printStackTrace();}
    }

    @Override
    public String getTestName() {
        return testName.get();
    }

    public void beforeEachTestMethod()
    {
        //This should be overridden.
    }

    @AfterMethod
    public void afterMethod(ITestResult result) {
        /*
            App force shutsdown under two conditions:
                1) If setup did not complete successfully in the @Before method
                2) If setupComplete = true, but test fails, check the custom restart flag and then reset the setupComplete flag shutdown app
                        By default restart flag is set to true
        */

        if(Globals.restartAppFlag) {        
            log.info("\n**********************************************\n"
                    + "AfterMethod called from BaseClass\n**********************************************\n");
System.out.println("Session id in BaseClass @AfterMethod is: " + driver.getSessionId());
            if ((!setupComplete) || ((setupComplete==true && Globals.testFailedStatus==true))){
                log.info("CP App will be shutdown due to above failure");
                //driver.quit();
                setupComplete = false;
            }
        }

    }

    @AfterClass(alwaysRun=true)
    public void afterClass() {      
        log.info("\n*******************************\n"
                + "After class cleanup executed from NTestSection\n*******************************\n");
        //Set setupComplete to false so that @BeforeMethod runs before each class
System.out.println("Session id in NTestSection @AfterClass is: " + driver.getSessionId());
        driver.quit();
System.out.println("Session id in NTestSection @AfterClass is: " + driver.getSessionId());
        setupComplete = false;
        stopAppiumService();        
    }

    @AfterTest
    public void cleanup() {
//      log.info("\n*******************************\n"
//              + "After test cleanup executed\n*******************************\n");    
    }   

    public void startAppiumService(String portNumber, String className) throws Exception {
        log.info("Starting Appium service. /n Logs will be stored at: " + appium_logfile_folder + "/appium_"+ className + ".log");
        File appium_logfile = new File(appium_logfile_folder + "\\appium_"+ className + ".log");

        try {
        AppiumServiceBuilder builder = new AppiumServiceBuilder();
        builder.withIPAddress("127.0.0.1");
        builder.usingPort(Integer.parseInt(portNumber));
        builder.withLogFile(appium_logfile);
        //builder.withArgument(GeneralServerFlag.LOG_LEVEL, LogLevel.ERR.toString());
        service = AppiumDriverLocalService.buildService(builder);
        service.start();
        service.clearOutPutStreams();
        log.info("Service has been started with port number " + portNumber);
        }catch(Exception e) {
            log.error("Error starting Appium service...");
            try {
                log.info("Retrying Appium service start");
                service.start();        
                service.clearOutPutStreams();
            }catch(Exception ae) {
                log.error("Error starting Appium service...: full stack trace follows:", ae);
                throw new Exception();
            }
        }
    }

    public void stopAppiumService() {

        Runtime runtime = Runtime.getRuntime();
        try {
            runtime.exec("taskkill /F /IM node.exe");
            runtime.exec("taskkill /F /IM cmd.exe");
            log.info("Appium service stopped...");
        } catch (IOException e) {
            log.error("Could not stop Appium service...");
            e.printStackTrace();
        }
    }

    public static boolean checkIfServiceIsRunning(int port) {
        boolean isServerRunning = false;
        ServerSocket serverSocket;
        try {
            serverSocket = new ServerSocket(port);
            serverSocket.close();
        }catch(IOException e) {
            //If control comes here, then it means that the port is in use
            isServerRunning = true;
        }finally {
            serverSocket = null;
        }
        if(isServerRunning)
            System.out.println("Appium service is already running on port: " + port);
        return isServerRunning;
    }
}

Link to Appium logs

(https://gist.github.com/skhandekar1/e4d300b1ae9619009191c0398e948b4d)

mykola-mokhnach commented 2 years ago

I don't see any errors in the server log. I believe it is some issue with the client code.

jlipps commented 2 years ago

That's too much code for us to adequately review. My advice would be to start stripping away everything except a bare skeleton from that code until you have a minimal reproducible scenario. It will probably make obvious what the issue is in your code.

prabhurohith commented 2 years ago

I was getting this issue but seems issues with dependency , as appium takes selenium dependencies transitively I removed a selenium java pom dependency as appium client had it, worked after that. Not sure if this helps but my 2 cents !!!