angular / protractor

E2E test framework for Angular apps
http://www.protractortest.org
MIT License
8.75k stars 2.31k forks source link

Feature Suggestion: Support multiple browser instances from single test #381

Closed bobthekingofegypt closed 9 years ago

bobthekingofegypt commented 10 years ago

Currently I am working on some socket based multi-player games using angular. In order to perform an e2e test I need to have two browsers talk to each other as the games wont start till 2 people join.

I was wondering if this could be something that is added on top of protractor. I kinda hacked up a proof of concept to allow me to test users joining and chatting on the game. https://github.com/bobthekingofegypt/protractor/commit/8ac849a5225698efc260d6e26307bccfe9801693

As a concept it appears to work ok, it allows you to write tests like

    var browserOne = protractors[0].browser;
    var browserTwo = protractors[1].browser;

    var elementTwo = protractors[1].element;

    var byOne = protractors[0].by;
    var byTwo = protractors[1].by;

    browserOne.get('http://127.0.0.1:4999/');
    browserTwo.get('http://127.0.0.1:4999/');

    var joinButton = browserOne.element(byOne.css('[value="Join"]'));
    joinButton.click();

    element(by.model('input.name')).sendKeys('Bob');
    browserOne.element(byOne.css('[value="OK"]')).click();

    var playerList = element.all(by.repeater('player in players'));
    expect(playerList.count()).toBe(1);

    var playerListOther = elementTwo.all(byTwo.repeater('player in players'));
    expect(playerListOther.count()).toBe(1);

This change breaks certain things like saucelabs support and it's not very complete but it is meant as a conversation starter.

It is quite a niche use case but is this something you would consider supporting in protractor?

juliemr commented 10 years ago

I'm so happy you brought this up, because a while ago we'd had a discussion and decided that no one would ever actually use that functionality!

So, you could do this now by basically rewriting runner.js (which is what you've started). I don't think there's any way to generalize your use case enough that it would be worth it to make it reusable, so I'd encourage you to just go ahead and write your own runner.js.

If anyone else would use this though, I'd love for them to chime in here!

tennisgent commented 10 years ago

We would definitely use this. We are writing a web app that allows insurance policy holders to communicate with construction contractors who are supposed to do repairs on the policy holders' homes.

Currently, we have to write e2e tests for each side of the app separately. We have to write one test that signs in as a policy holder, does some setup and then sends off a communication to the contractor. And then we have to log out as a policy holder, log in as a contractor, and then make sure that the communication arrived in the contractor's inbox as expected.

However, if the functionality described above were possible, then we could run two parallel versions of the browser at the same time where one is signed in as a policy holder and the other is signed in as a contractor and we could instantly verify that the communications can go back and forth as expected.

That would be a huge benefit to us. I can imagine there are many other such websites out there that could use this type of functionality.

christothes commented 10 years ago

I'd like to use this as well. I'd like to test a game that needs multiple instances to exercise all scenarios

hanssgo commented 10 years ago

Would definitely use this as well! Been logging out and logging in as different users as well, with this support we would no longer have to do that =)

christothes commented 10 years ago

@juliemr Curious why this couldn't be generalized?

I was thinking it might make more sense if the concept of a "primary driver" remained, with a config option to specify n number of "helper drivers" that could be kept in an array similar to the example from @bobthekingofegypt. Would this approach make it any less intrusive to the mainline use case while allowing the multi-driver flexibility?

Granted I don't understand how saucelab compatibility comes into play here. However, if two two concepts simply aren't compatible, that could be accounted for in the config logic.

I wouldn't mind attempting a PR.

elgalu commented 10 years ago

I need this to test instant messaging and notifications between two logged in users.

My suggestion is similar to @bobthekingofegypt but using blocks instead of elementOne, elementTwo

And having browser1, browser2, ... kind of globals:

By default browser would point to browser1 and all expectations would run by default on browser1 unless browser2 is specified with some DSL like browser2.run(..{...}...); making the feature backward compatible.

Sample usage of browser2

it('works with 2 logged in users', function() {
  browser1.get('page1');
  browser2.get('page2');
  expect($('.div').getText()).toContain('hi'); // always defaults to browser1
  browser2.run(function() {
    $('button#send').click();
    expect($('.div').getText()).toContain('hello you');
  });
});
adamduffy commented 10 years ago

+1 need for this.

hanssgo commented 10 years ago

+1. Would be able to use this as well. Saves having to log out and log in as different users. On Mar 12, 2014 10:22 AM, "adamduffy" notifications@github.com wrote:

+1 need for thtis.

Reply to this email directly or view it on GitHubhttps://github.com/angular/protractor/issues/381#issuecomment-37437150 .

caitp commented 10 years ago

/sub

elgalu commented 10 years ago

Will leave a reminder here that this feature won't work on IE: https://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_Can_I_run_multiple_instances_of_the_WebDriver_sub-classes?

Not sure about Safari, probably won't support this either.

bobthekingofegypt commented 10 years ago

It was a while ago that I did this, and I haven't been working with Angular/protractor since so it is a bit hazy.

But looking at my diff I don't think I did anything beyond what was required to make a proof of concept work. All the promise work is because you need to block till all sessions are ready, and then make sure all sessions terminate at the end of the test. I also left in some code so single browser tests would work as normal. The fork does work, I used it for two projects; it does have flaws though.

I haven't tracked the changes in protractor but if what you want to do is simple you can try checking out my fork and npm link it, I changed the name from protractor to protractor-multi so I could run them both at the same time.

toedter commented 10 years ago

+1 for this. I would like to test asynchronous pub/sub with 2 browsers.

Mikrovolnovka commented 10 years ago

You can generalize this with implementing the ability of parallelization tests with scaling that is not limited to 2.

Sometimes we implement tests that are not affecting each other and can be run in parallel. We would like to have ability of specifying different sets of specs. I guess this should be possible both with Selenium Standalone server and direct usage of drivers. I tried to run 2 different instances of protractor on single local machine (but in different bash windows, hehe) at a time using one instance of Selenium Standalone server and they worked in parallel well.

So, the POC is to implement running parallel tests within single protractor instance (with single output and ability to collect results to single report). Maybe there can be way to use something like Async.JS to run different specs in threads.

Maybe there is a way to do that with some Grunt package, but it is not convenient to have many Protractor config files, we would like to have all configs in one file.

Thanks in advance for your reply.

orcaman commented 10 years ago

+1 for this (writing a websockets app that could benefit from this)

rafaelbattesti commented 10 years ago

+1 still needing some support... @juliemr Could you give me some directions?

From issue #569...

I need 2 instances of browser running within a single test and must be able to manipulate them individually. I haven't found any info on how to perform it. Would be glad if you could enlighten me. This is a definite roadblock for my test suite. Thanks.

Thanks @hankduan. The problem I see is. If I use multiCapabilities, I can't manipulate both instances as I want to. The action of a user in one browser must reflect in the other browser. Like in google docs, when a user writes in a shared document, the other can see what is happening. This is basically what I must test. And if the second user deletes what had been written beforehand, the first must see it.

Mikrovolnovka commented 10 years ago

@rafaelbattesti wrote: "Thanks @hankduan. The problem I see is. If I use multiCapabilities, I can't manipulate both instances as I want to. The action of a user in one browser must reflect in the other browser. Like in google docs, when a user writes in a shared document, the other can see what is happening. This is basically what I must test. And if the second user deletes what had been written beforehand, the first must see it."

What about using "wait" functions. For about, you can implement something like "waitForElement" and in one spec file (1) start to wait with your timeout (e.g. 30s), in other spec file (2) run test that should reflect the first browser. spec file (2) makes actions and after them new element appears. spec file (1) sees that his element appears and continues his actions. Does this make sense?

hankduan commented 10 years ago

@rafaelbattesti If your backend is not mocked out, you can give @Mikrovolnovka's method a try. It might be a little flakey some times, but that is the best solution for the use case at the moment as we do not have any "official" way of doing this right now.

nnaffar commented 10 years ago

+1 . I would like to test client 2 client web app (#949). does anyone knows how can i do it? thanks

carlhopf commented 10 years ago

+1 as this would be very very useful

Elijen commented 10 years ago

+1

ntrrgc commented 10 years ago

+1

selahlynch commented 10 years ago

Hi, we would use this too! http://simulations.wharton.upenn.edu/

noamokman commented 10 years ago

+1

lam2558 commented 10 years ago

This might be a work around. Please let me know if this a a good way of doing it or not.

conf.js
  multiCapabilities: [
  {  browserName: 'chrome', },
  {  browserName: 'firefox',  }],

test.js
it('open login page', function () {
    var browserType = null;
    console.log("browser base URL =<<  "+ browser.baseUrl);
    browser.getCapabilities().then(function (cap) {
      console.log(cap.caps_.browserName);
      browserType = cap.caps_.browserName;
    });
  });

it('test something with two browser open up simultaneously', function (){
if(browserType == 'firefox') { login as user1 and send message "hello" }
if(browserType == 'chrome') {login as user2 and wait for message "hello" and response "hi"}
});

I'm essentially opening two different browser and in the same test script, defining what each browser should act as different user.

moskrc commented 10 years ago

+1

ceelian commented 10 years ago

I am not sure if I solved the exact same problem already a year ago.

We also had an interacting socket.io app. We wrote the protractor e2e tests one year ago, so the code is right now only compatible with a very early version of protractor (0.12 if I remember it right).

The following code is just a small demo code for a blog post I wrote a year ago about our first steps with protractor in an interacting dual browser setup (Source in german http://blog.cnc.io/allgemein/e2e-tests-in-angularjs-with-protractor/)

Maybe we can translate this code to work with version 1.3.1 and have this feature out of the box?

/*###############################################
 Start the selenium server:
 java -jar selenium/selenium-server-standalone-2.35.0.jar \
 -Dwebdriver.chrome.driver=node_modules/chromedriver/bin/chromedriver

 Start the tests:
 jasmine-node --verbose path/to/my/e2e/tests/example-spec.js

//###############################################*/
//It is important to use the protractor selenium-webdriver
var webdriver = require('protractor/node_modules/selenium-webdriver');
var protractor = require('protractor');
require('protractor/jasminewd');

describe('yeoman angularjs generator app', function() {

    //create a driver which starts a chrome browser
    var driver1 = new webdriver.Builder()
            .usingServer('http://localhost:4444/wd/hub')
            .withCapabilities(webdriver.Capabilities.chrome()).build();

    //create another driver which starts a firefox browser
    var driver2 = new webdriver.Builder()
            .usingServer('http://localhost:4444/wd/hub')
            .withCapabilities(webdriver.Capabilities.firefox()).build();

    //set timeout and wrap the webdriver instance in a protractor instance
    driver1.manage().timeouts().setScriptTimeout(15000);
    var ptor1 = protractor.wrapDriver(driver1);

    driver2.manage().timeouts().setScriptTimeout(15000);
    var ptor2 = protractor.wrapDriver(driver2);

    //the test scenario
    it('should have a list of awesome things', function() {
        //load web application in both browser
        ptor1.get('http://localhost:9000/?myspecialparam=forbrowserone');
        ptor2.get('http://localhost:9000/?myspecialparam=forbrowsertwo');

        //run test in first browser (chrome)
        var thingslistone = ptor.findElements(
                    protractor.By.repeater('thing in awesomeThings'));
        thingslistone.then(function(arr) {
            expect(arr.length).toEqual(3);
            expect(arr[0].getText()).toEqual('HTML5 Boilerplate');
            expect(arr[1].getText()).toEqual('AngularJS');
            expect(arr[2].getText()).toEqual('Karma');
        });

        //run test in second browser (firefox)
        var thingslisttwo = ptor2.findElements(
                protractor.By.repeater('thing in awesomeThings'));
        thingslisttwo.then(function(arr) {
            expect(arr.length).toEqual(3);
            expect(arr[0].getText()).toEqual('HTML5 Boilerplate');
            expect(arr[1].getText()).toEqual('AngularJS');
            expect(arr[2].getText()).toEqual('Karma');
        });

    }, 20000);

    //needed to quit the browser after all tests are executed
    it('afterAll', function() {
        driver1.quit();
        driver2.quit();
    })
});
lam2558 commented 10 years ago

This is my thought:

Since I am using multiCapabilities to start multiple threads, the global browserInstanceCount variable in protractor.conf may not be shared by all threads.

I am trying a new way of identifying browsers so that it no longer rely on the browser type. The new approach is to assign ticket number to new browser. The ticket number is saved in a text file and will accumulate every time it is read by a new browser. So now browser is identified by ticket number instead of browser type. I tried with 4 same or mixed browser types and it works so far. The file, however, has to be removed by Jenkins or manually each time before script starts.

The ticket management is written in the conf.js : var location = "ticket";

if(fs.existsSync(location)){ //when file exists, add 1 for the new thread var number = null; number - fs.readFileSync(location).toString(); number = parseInt(number) + 1; fs.writeFileSync(location, number); browser.params.ticketNumber = number; } else{ // when file does not exists, assign 1 for the first thread browser.params.ticketNumber = "1"; fs.writeFileSync(location, browser.params.ticketNumber); }

In your spec: if(browser.params.userTicket == "1") { // do steps for browser #1 } else if(browser.params.userTicket == "2"){ // do steps for browser #2 }....

If the global variable in protractor.conf works then your solution is better since no file management required. Please try it out and let us know.

On Thu, Oct 16, 2014 at 8:51 AM, carlhopf notifications@github.com wrote:

Quickly hacked something together on top of angular 1.3.1, to start and get a second global browser object. Works fine so far! Be warned: the code is extremely ugly and only works for 'chromeOnly: true' configs.

Just wanted to share the code, in case anyone needs a starting point in getting a second browser instance running.

What about a protractor.conf option like 'browserInstanceCount', then simply launch this many instances of drivers for each running test suite and make them available as a global browsers[] array?

carlhopf@e92e62e https://github.com/carlhopf/protractor/commit/e92e62eff80ebfb4f8e2ebb1e39b4699170d55fc

To test:

git clone https://github.com/carlhopf/protractor.git cd protractor npm install npm link webdriver-manager update

— Reply to this email directly or view it on GitHub https://github.com/angular/protractor/issues/381#issuecomment-59384113.

carlhopf commented 10 years ago

Sorry, I've had to delete my old comment due to a bug. Here is my new take on the problem to allow multiple browsers windows/instances for protractor 1.3.1: https://github.com/carlhopf/protractor/commit/dd76f72bcc2e6fc338b95e3465a19034b4591241

This allows to call

it('should create, test, and close a new browser window', function() {
    var browser2 = newBrowser();
    browser2.get('http://localhost:8000');
    expect(browser2.element(by.css('my-missing-element')).isPresent()).toBe(false);
    quitBrowser(browser2);
});

and you'll get a new browser window to work with (supports as many as you'd like to use). All new windows will also automatically close when protractor quits.

NOTE: you must add "chromeOnly: true;" to protractor.conf.js, I've just made lib/driverProviders/chome.js compatible so far!

Test it out yourself:

git clone https://github.com/carlhopf/protractor
cd protractor
npm install
npm link
webdriver-manager update
carlhopf commented 10 years ago

Here is another workaround i'd like to propose for multiple windows/instances for protractor 1.3.1: https://github.com/carlhopf/protractor/commit/9aa1a7e4b10ca3becd699ea7f3ae63158dfd8b9e

This gives you newBrowser(), quitBrowser(index) and switchBrowser(index) and assigns the currently selected browser to the global protractor objects (browser, element, protractor.getInstance(), ...). So there is no need to change test syntax, simply switchBrowser() and continue as before.


describe('should open 2 browsers', function() {
    beforeEach(function() {
        newBrowser();
    });

    it('should open two windows', function() {
        browser.get('http://localhost:8001');

        switchBrowser(1);
        browser.get('http://127.0.0.1:8001');

        browser.executeAsyncScript(function(callback) {
            callback(window.location.href.indexOf('127.0.0.1') !== -1);
        }).then(function(res) {
            expect(res).toBe(true);
        });

        switchBrowser(0);

        browser.executeAsyncScript(function(callback) {
            callback(window.location.href.indexOf('127.0.0.1') === -1);
        }).then(function(res) {
            expect(res).toBe(true);
        });
    });

    afterEach(function() {
        quitBrowser(1);
        expect(browsers.length).toBe(1);
    });
});

NOTE: you must add "chromeOnly: true;" to protractor.conf.js, I've just made lib/driverProviders/chome.js compatible so far!

Test it out yourself:

npm install -g git://github.com/carlhopf/protractor.git#switchbrowser
webdriver-manager update
Bessonov commented 10 years ago

"I'm so happy you brought this up, because a while ago we'd had a discussion and decided that no one would ever actually use that functionality!"

I think at least every "real-time" app need this testing functionality. At least for chrome there is no problem to open new window through: var win = prt.driver.executeScript('window.open("http://localhost/", "windowName", "width=1024,height=768");'); (see http://stackoverflow.com/a/726803/926620 ). But the problem then is shared session. To overcome this limitation I use different hostname (for two windows 127.0.0.1 and localhost), like described in http://stackoverflow.com/a/23551878/926620 .

This feature is very important for me too.

ceelian commented 9 years ago

Got my multi-browser test to run again with the "vanilla" protractor package. Perfect for chat or any other multi-window live interaction web project testing.

Just had to change the following includes:

var webdriver = require('selenium-webdriver');
var protractor = require('protractor');
require('jasminewd');

The full example code is:

/*###############################################
 Start the selenium server:
 java -jar selenium/selenium-server-standalone-2.35.0.jar \
 -Dwebdriver.chrome.driver=node_modules/chromedriver/bin/chromedriver

 Start the tests:
 jasmine-node --verbose path/to/my/e2e/tests/example-spec.js

//###############################################*/
var webdriver = require('selenium-webdriver');
var protractor = require('protractor');
require('jasminewd');

describe('yeoman angularjs generator app', function() {

    //create a driver which starts a chrome browser
    var driver1 = new webdriver.Builder()
            .usingServer('http://localhost:4444/wd/hub')
            .withCapabilities(webdriver.Capabilities.chrome()).build();

    //create another driver which starts a firefox browser
    var driver2 = new webdriver.Builder()
            .usingServer('http://localhost:4444/wd/hub')
            .withCapabilities(webdriver.Capabilities.firefox()).build();

    //set timeout and wrap the webdriver instance in a protractor instance
    driver1.manage().timeouts().setScriptTimeout(15000);
    var ptor1 = protractor.wrapDriver(driver1);

    driver2.manage().timeouts().setScriptTimeout(15000);
    var ptor2 = protractor.wrapDriver(driver2);

    //the test scenario
    it('should have a list of awesome things', function() {
        //load web application in both browser
        ptor1.get('http://localhost:9000/?myspecialparam=forbrowserone');
        ptor2.get('http://localhost:9000/?myspecialparam=forbrowsertwo');

        //run test in first browser (chrome)
        var thingslistone = ptor.findElements(
                    protractor.By.repeater('thing in awesomeThings'));
        thingslistone.then(function(arr) {
            expect(arr.length).toEqual(3);
            expect(arr[0].getText()).toEqual('HTML5 Boilerplate');
            expect(arr[1].getText()).toEqual('AngularJS');
            expect(arr[2].getText()).toEqual('Karma');
        });

        //run test in second browser (firefox)
        var thingslisttwo = ptor2.findElements(
                protractor.By.repeater('thing in awesomeThings'));
        thingslisttwo.then(function(arr) {
            expect(arr.length).toEqual(3);
            expect(arr[0].getText()).toEqual('HTML5 Boilerplate');
            expect(arr[1].getText()).toEqual('AngularJS');
            expect(arr[2].getText()).toEqual('Karma');
        });

    }, 20000);

    //needed to quit the browser after all tests are executed
    it('afterAll', function() {
        driver1.quit();
        driver2.quit();
    })
});

I have not tested it a lot, i just had the time to get it running again. Any suggestions are welcome if there are some bugs or misconceptions in the code.

hankduan commented 9 years ago

Implemented with https://github.com/angular/protractor/commit/0bbfd2b6d38392938781d846ad37b5a0fd964004. This will be released in protractor 1.5.0

svapreddy commented 9 years ago

@hankduan Can I get a documentation link for how to control multiple browsers for instant messaging kind of applications. I have tried searching for this and I am unable to find some code except multiCapabilities.

hankduan commented 9 years ago

note to self: I should work on the docs.

For now, read the api annotation: https://github.com/angular/protractor/blob/master/lib/runner.js#L190 There are also a lot of examples here: https://github.com/angular/protractor/blob/master/spec/interaction/interaction_spec.js

nishankkumar1994 commented 5 years ago

Please make sure to install npm install protractor

Update web driver manager webdriver-manager update

Run this command from your root node node_modules\protractor\bin\webdriver-manager update

Now start up a server with: webdriver-manager start

Also make sure that your protractor.conf.js file has below line // baseUrl: 'http://localhost:4200/', seleniumAddress: 'http://localhost:4444/wd/hub/',

Now run your e2e tests on different browesers ng e2e