oracle / node-oracledb

Oracle Database driver for Node.js maintained by Oracle Corp.
http://oracle.github.io/node-oracledb/
Other
2.26k stars 1.07k forks source link

Protractor oracledb connection failing with NoSuchSessionError #1002

Closed bhoppeadoy closed 5 years ago

bhoppeadoy commented 5 years ago

Node v8.9.4 node-oracledb v3.0.0 protractor v5.4.0

I'm trying to run a data-driven protractor test. First thing is run a query against oracle table to return a set of data. Then loop over that data calling other specs. The oracle connection is failing without an error. So I run in debug mode I get this uncaught exception:

NoSuchSessionError: This driver instance does not have a valid session ID (did you call WebDriver.quit()?) and may no longer be used.
    at session_.flow_.promise (C:\Users\me\repos\wbhtml5\designer-e2e\node_modules\selenium-webdriver\lib\webdriver.js:847:16)
    at new Promise (<anonymous>)
    at SimpleScheduler.promise (C:\Users\me\repos\wbhtml5\designer-e2e\node_modules\selenium-webdriver\lib\promise.js:2242:12)
    at promise.finally (C:\Users\me\repos\wbhtml5\designer-e2e\node_modules\selenium-webdriver\lib\webdriver.js:846:34)
    at promise.then.error (C:\Users\me\repos\wbhtml5\designer-e2e\node_modules\selenium-webdriver\lib\promise.js:1684:12)
I can't figure out why I'm getting Promise errors or session errors. I'm not using the browser/webdriver yet I just want to connect to oracle.

Here's my test spec that protractor is running that's setting up the connection:

test.js

import ProductFolderSpec from '../../specs/product-folder.e2e-spec';
import dbms = require('../../database');

// Connect to oracle and get list of parts
dbms.getProductFolderPartsCallback(dbms.PFPartKind.Switcher, dbms.PFPartStatus.Production, runProductFolderCallback);

// Callback function to loop over list of parts
function runProductFolderCallback(err, result) {
  const partsToTest = JSON.parse(result);
  partsToTest.forEach(part => {
     ProductFolderSpec.OpenProductFolder(part);
  });
}

This is database.js

import Common from './common';
import credentials = require('./credentials');
import oracledb = require('oracledb');

export function getProductFolderPartsCallback(partType: PFPartKind, partStatus: PFPartStatus, callback) {
  const dbms = oracledb;
  dbms.fetchAsString = [ oracledb.CLOB ];

  const cred = credentials.getCredential(credentials.TestCredentialKind.SomeDB);

  dbms.getConnection({
    user: cred.Userid,
    password: cred.Password,
    connectString: cred.ConnectionString
  },
    function (connErr, conn) {
      if (connErr) {
        Common.error(`getProductFolderParts: connection error: ${connErr}.`, true);
        return callback(connErr);
      }

      const sqlCmd = 'SELECT PART_JSON FROM WEBENCHDB.ODT_PRODUCT_FOLDER_PARTS';

      conn.execute(sqlCmd, function (queryErr, result) {
        if (queryErr) {
          Common.error(`getProductFolderParts: query error: ${queryErr}`, true);
          closeConnection(conn);
          return callback(queryErr);
        }

        let resultJSON = '';

        (result.rows as any[]).forEach(row => {
          if (resultJSON.length === 0) {
            resultJSON = `[ ${row[0]}`;
          } else {
            resultJSON = resultJSON + `, ${row}`;
          }
        });

        resultJSON = resultJSON + ']';

        closeConnection(conn);

        return callback(null, resultJSON);
      }
      );
    });
}

function closeConnection(conn) {
  conn.close(function (err) {
    if (err) {
      Common.error(`getProductFolderParts: connection close error: ${err}`, true);
    } else {
      Common.info(`getProductFolderParts: connection closed`, true);
    }
  });
}

Is there another way that I'm supposed to set this up?

dmcghan commented 5 years ago

@bhoppeadoy Can you show us verify-design.e2e-spec?

Also, is this needed/correct?

        let resultJSON = '';

        (result.rows as any[]).forEach(row => {
          if (resultJSON.length === 0) {
            resultJSON = `[ ${row[0]}`;
          } else {
            resultJSON = resultJSON + `, ${row}`;
          }
        });

        resultJSON = resultJSON + ']';

Maybe this is better?

resultJSON  = JSON.stringify(result.rows);
bhoppeadoy commented 5 years ago

@dmcghan Good point on the JSON.stringify(), not sure why the developer did it that way.

I've updated the example to call ProductFolderSpec.OpenProductFolder(). Basically what I need the test to do is get a list of parts and their associated properties from an oracle database. Then for each part run the below spec.

product-folder.e2e-spec.ts

import 'mocha';
import { expect } from 'chai';
import ProductFolderPage from '../pages/product-folder.po';
import Common from '../common';
const globals: any = global;

export default class ProductFolderSpec {
  static OpenProductFolder(part) {
    describe('Product Folder Page : ', function() {
      let pfFailure = false;

      before(function() {
        globals.currentSpec = this;
        Common.info('[BEGIN DESCRIBE] ' + this.test.parent.title);
        // Reset global error
        globals.fatalError = false;
      });

      beforeEach(function() {
        globals.currentSpec = this;
        Common.info('[BEGIN SPEC] ' + this.currentTest.title);
        if (pfFailure) {
          Common.warn(' - This test skipped due to previous error.');
          this.currentTest.fn = function() { this.skip(); };
        }
      });

      afterEach(async function() {
        if (this.currentTest.state === 'failed') {
          pfFailure = true;
          await Common.createScreenshot();
        }
        Common.info('[END SPEC] ' + this.currentTest.title + ' - ' + this.currentTest.state);
      });

      after(function() {
        if (pfFailure) {
          globals.fatalError = true;
        }
        Common.info('[END DESCRIBE] ' + this.test.parent.title);
      });

      it('Should open Product Folder page.', async function() {
        this.timeout(60000);
        await ProductFolderPage.navigateTo(part.properties.ProductFolderLink);
      });

      if (part.properties.production === 'Y') { // Production Part
        it('Should verify product folder page exists', async function() {
          const isValid = await ProductFolderPage.isPageValid();
          expect(isValid).to.be.equal(true, 'Product Folder page does not exist.');
        });

        it('Part should not be NRND', async function() {
          const isNRND = await ProductFolderPage.isPartNRND();
          expect(isNRND).to.be.equal(false, 'Part is Not Recommended for New Designs.');
        });

        it('Should have panel', async function() {
          const isPresent = await ProductFolderPage.isPanelPresent();
          expect(isPresent).to.be.equal(true, 'Panel does not exist.');
        });
      } else { // Development Part
        it('Should not have panel for development part', async function() {
          const validPage = await ProductFolderPage.isPageValid();
          if (validPage) {
            const isPresent = await ProductFolderPage.isPanelPresent();
            expect(isPresent).to.be.equal(false, 'Panel exists for development part.');
          }
        });
      }
    });
  }
cjbj commented 5 years ago

Just checking: you're using node-oracledb in the mid-tier right, not in Angular? See https://github.com/oracle/node-oracledb/issues/957#issuecomment-410264998

dmcghan commented 5 years ago

@bhoppeadoy Try commenting out this line in product-folder.e2e-spec.ts.

await Common.createScreenshot();
bhoppeadoy commented 5 years ago

Just checking: you're using node-oracledb in the mid-tier right, not in Angular? See #957 (comment)

No, this is not in an Angular project. This is in a Protractor/Selenium End-to-End test.

bhoppeadoy commented 5 years ago

@bhoppeadoy Try commenting out this line in product-folder.e2e-spec.ts.

await Common.createScreenshot();

@dmcghan it never reaches that code. The exception is thrown in dbms.getConnection(). For simplification, you can remove product-folder.e2e-spec.ts from the call and just have this:

// Connect to oracle and get list of parts
dbms.getProductFolderPartsCallback(dbms.PFPartKind.Switcher, dbms.PFPartStatus.Production, runProductFolderCallback);

// Callback function to loop over list of parts
function runProductFolderCallback(err, result) {
  const partsToTest = JSON.parse(result);
}
dmcghan commented 5 years ago

@bhoppeadoy How do you know where the exception is thrown. I don't see any line numbers in the error stack you showed. Maybe we're missing the output you're seeing?

Earlier you said:

The oracle connection is failing without an error.

What makes you believe this is true?

bhoppeadoy commented 5 years ago

I'm not getting any type of error printed to the console when this is ran, so I'm not sure what exactly is failing. When I run in debug mode, I put a breakpoint in database.js at

  dbms.getConnection({

and one at

    function (connErr, conn) {

After continuing from the first breakpoint I get the uncaught exception thrown. It never reaches the second breakpoint. The exception is thrown from webdriver.js

dmcghan commented 5 years ago

@bhoppeadoy I still don't see how that the Oracle connection is failing without an error. As you say, the error is from webdriver.js, not node-oracledb.

Could you please show us common.js? I'm guessing that even if you never get to the point of executing createScreenshot, there's some initialization code that's causing the problem.

bhoppeadoy commented 5 years ago

@dmcghan I even removed common.js to see if that was doing it but same issue. I agree that someting is being initialized as part of the test that is causing it. Probably something inherent with how Protractor/Selenium/Webdriver works that is causing it.

I moved the call to getProductFolderParts() in the onPrepare() function of the protractor.conf.js file. This way the oracle connection is called before any other Protractor setup has happened. From there, it works and I get back the data that I'm wanting.

This should solve my issue now. Thanks for working through this with me.

dmcghan commented 5 years ago

@bhoppeadoy I'm glad you got it working!