Protractor: Use protractor as a library

Created on 17 Mar 2017  路  4Comments  路  Source: angular/protractor

Hi All

Is there any official statement on how to use protractor as a library? I need to have my own runner/lifecycle control mechanism but still take advantage of the syntactic sugar provided by the API.

If I understand correctly, the whole ProtractorBrowser initialization is entangled with the Runner, as the latter sets many theoretically private properties in the former. Even if I simply try to leverage run()/createBrowser(), it will still demand things like a framework adapter, which is unnecessary if I'm using my own runner.

So far, I believe I could extract the necessary bits from run()/createBrowser(), but that's very likely to break on every new release.

Any suggestions would be much appreciated.

Regards

feature request

Most helpful comment

Heya, we have a similar need in Angular CLI. Right now we resort to doing something like:

const protractorLauncher = require('protractor/built/launcher');
protractorLauncher.init(configPath, additionalProtractorConfig));

(this happens in https://github.com/angular/angular-cli/blob/master/packages/%40angular/cli/tasks/e2e.ts)

It's generally ok but the launcher is not a public API, and it calls process.exit() a lot. This means that our usage is not composable or unit-testable.

A naive approach that would work for us is to provide a custom callback that overrides the process.exit() calls. That way we could do stuff and exit outside. I don't know if this is enough generally but it's a though.

All 4 comments

Great question! I think this is a bit of a dark spot in the docs. I don't actually use protractor in this way but I can see it being really helpful. Would you be up for creating an example or a spec of what you'd like to see to for discussion?

I think it would be great to try and split things out to make the core itself more modular and re-usable. At the moment it tends to be focused on "protractor for protractor's sake".

This is a bit of an "epic" issue that we should probably split into smaller tasks but let's leave this open to discussion for now.

I think this is a great idea, and is probably the direction things will go (it's certainly what @seanmay would like to get to). If you want custom synchronization logic, you could just write a regular WebDriver test and use BlockingProxy to inject behavior before each WebDriver command. But it would still be nice if you could use element finders or expected conditions with your own runner.

Hi @NickTomlin, I was fiddling with a custom context on Sencha Test, and this is the kind of boilerplate code I came out with, give or take optional config properties:

ST.configureContext({
    init: function () {
        var me = this,
            webdriver = require('selenium-webdriver'),
            protractor = require('protractor/built/ptor').protractor,
            driverProviders = require('protractor/built/driverProviders'),
            plugins = require('protractor/built/plugins'),
            cfg = me.driverConfig,
            host = cfg.host,
            port = cfg.port,
            caps = cfg.desiredCapabilities,
            driver, browser, initProperties, config, originalExpect,
            blockingProxyUrl = null;

        // TODO user/password
        driver = me.driver = new webdriver.Builder()
            .withCapabilities(caps)
            .usingServer('http://' + host + ':' + port + '/wd/hub')
            .build();

        require('jasminewd2').init(driver.controlFlow(), webdriver);

        // TODO this/driverConfig
        config = {
            allScriptsTimeout: 30 * 1000
        };

        initProperties = {
            baseUrl: config.baseUrl,
            rootElement: config.rootElement,
            untrackOutstandingTimeouts: config.untrackOutstandingTimeouts,
            params: config.params,
            getPageTimeout: config.getPageTimeout,
            allScriptsTimeout: config.allScriptsTimeout,
            debuggerServerPort: config.debuggerServerPort,
            ng12Hybrid: config.ng12Hybrid,
            waitForAngularEnabled: true
        };

        browser = new protractor.ProtractorBrowser(
            driver,
            initProperties.baseUrl,
            initProperties.rootElement,
            initProperties.untrackOutstandingTimeouts,
            blockingProxyUrl
        );

        browser.plugins_ = new plugins.Plugins({});
        browser.ready =
            browser.ready
            .then(function () {
                return browser.waitForAngularEnabled(initProperties.waitForAngularEnabled);
            })
            .then(function () {
                return driver.manage().timeouts().setScriptTimeout(initProperties.allScriptsTimeout);
            })
            .then(function () {
                return browser;
            });

        protractor.browser = browser;
        protractor.$ = browser.$;
        protractor.$$ = browser.$$;
        protractor.element = browser.element;
        protractor.by = protractor.By = protractor.ProtractorBrowser.By;
        protractor.ExpectedConditions = browser.ExpectedConditions;

        global.browser = browser;
        global.$ = browser.$;
        global.$$ = browser.$$;
        global.element = browser.element;
        global.by = global.By = protractor.By;
        global.ExpectedConditions = protractor.ExpectedConditions;

        global.protractor = protractor;

        return browser.get(me.subjectUrl);
    },

    stop: function () {
        return protractor.browser.quit();
    }
});

This is of course a fragile and invasive code, very likely to break at any moment in a new release.

Now, consider the following assumptions:

  • Execution environment (Node.js sandbox) is up and running and will be shut down at the appropriate time
  • Jasmine is loaded
  • Test suites and additional libraries are loaded
  • Jasmine reporters are loaded and wired
  • selenium-webdriver client is instantiated

All I would ideally need is:

var protractor = require('protractor'),
    browser = protractor.wrap(driver); // get in sync with the control flow, etc
protractor.install('jasmine'); // set globals, do the jasminewd2 business, etc

And then

browser.quit();

I know the .wrap() method was there until some time ago, and looks like it was removed because it was becoming kind of a headache to maintain, but I would love to see it back in the API somehow.

P.S.: ST + Protractor working together: https://gist.github.com/marcelofarias/98f7531fa4d9ab9c5620fcdd3defc461#file-senchatest_protractor-png

Regards

Heya, we have a similar need in Angular CLI. Right now we resort to doing something like:

const protractorLauncher = require('protractor/built/launcher');
protractorLauncher.init(configPath, additionalProtractorConfig));

(this happens in https://github.com/angular/angular-cli/blob/master/packages/%40angular/cli/tasks/e2e.ts)

It's generally ok but the launcher is not a public API, and it calls process.exit() a lot. This means that our usage is not composable or unit-testable.

A naive approach that would work for us is to provide a custom callback that overrides the process.exit() calls. That way we could do stuff and exit outside. I don't know if this is enough generally but it's a though.

Was this page helpful?
0 / 5 - 0 ratings