Protractor: ๊ฐ๋„๊ธฐ ๋ฐ ๊ฐ๋„ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ

์— ๋งŒ๋“  2013๋…„ 08์›” 30์ผ  ยท  49์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: angular/protractor

๊ฐ๋„๊ธฐ 0.8์„ ์‹คํ–‰ํ•  ๋•Œ ๋ฌธ์ œ๊ฐ€ ์žˆ๊ณ  ์ˆ˜๋™์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋œ ์•ฑ์—์„œ ๊ฐ๋„๊ธฐ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ฐ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฐ ์„ค์ •์˜ ๊ด€๋ จ ๋ถ€๋ถ„์„ ๋ณด์—ฌ์ฃผ๋Š” ๋ช‡ ๊ฐ€์ง€ ์Šค๋‹ˆํŽซ์ž…๋‹ˆ๋‹ค.
index.html:

 <script type="text/javascript" data-main="js/main" src="js/libraries/require.js"></script>

js/main.js:

require({
    shim: {
        .... snip ..
            }
           }, 
  ['require', ..... ], function(require) {
    return require(['bootstrap']);

๋ถ€ํŠธ์ŠคํŠธ๋žฉ.js:

var appElm = $('html');
appElm.addClass('ng-app'); // this
angular.bootstrap(document, ['app']);

์ด๊ฒƒ์€ ์‹ค์ œ๋กœ ์ž‘๋™ํ•˜๋ฉฐ Angular๋Š” ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ์‹์œผ๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. html ์†Œ์Šค๋ฅผ ๋ณผ ๋•Œ html ์š”์†Œ์— ๋‚˜ํƒ€๋‚˜๋Š” ng-app ์†์„ฑ์„ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ฐ๋„๊ธฐ๋„ ๊ทธ๋ ‡๊ฒŒํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๊ฐ๋„๊ธฐ ์ƒ˜ํ”Œ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜๊ณ  ๊ด€๋ จ ๊ฐ’์„ ๋‚ด ์•ฑ์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค.

specs: [
    '../src/test/javascript/protractorSpec/*_spec.js',
  ],
  baseUrl: 'http://localhost:8080/Viewer/',
  rootElement: 'html',

์ƒ˜ํ”Œ ํ…Œ์ŠคํŠธ:

describe('List SMC Charts', function() {
        var ptor= protractor.getInstance();

        beforeEach(function() {
          ptor.get('#/smcChart');
        });

        it('list, no filter', function() {
          var selectOption = ptor.findElement(ptor.By.input("selectOption"));
        });
});

์ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋‘ ๊ฐ€์ง€ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

 1) List SMC Charts   list, no filter
   Message:
     Error: Angular could not be found on the page http://localhost:8080/Viewer/#/smcChart
   Stacktrace:
     Error: Angular could not be found on the page http://localhost:8080/Viewer/#/smcChart
    at /usr/local/lib/node_modules/protractor/lib/protractor.js:392:15

๊ทธ๋ฆฌ๊ณ 

2) List SMC Charts list, no filter
   Message:
     TypeError: Cannot call method 'input' of undefined
   Stacktrace:
     TypeError: Cannot call method 'input' of undefined
    at null.<anonymous> (/Users/........//Viewer/src/test/javascript/protractorSpec/some_spec.js:9:55)

์ฒซ ๋ฒˆ์งธ ์˜ค๋ฅ˜๊ฐ€ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ ๊ฐ™์ง€๋งŒ ๋งˆ์ง€๋ง‰ ์˜ค๋ฅ˜๋„ ๊ฝค ํฅ๋ฏธ๋กญ์Šต๋‹ˆ๋‹ค. ptor.By.input ์ค„์ด ์ž‘๋™ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์–ด๋–ค ์•„์ด๋””์–ด?

untriaged discussion

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

@sjelin ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์ง€์›ํ•˜๋Š” ๋ฌธ์„œํ™”๋œ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๊ฐ๋„๊ธฐ 5.0์— ๋Œ€ํ•œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  49 ๋Œ“๊ธ€

Angular ์ฝ”๋“œ๊ฐ€ ํŽ˜์ด์ง€์— ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๊นŒ? shim, bootstrap ๋ฐ ๊ธฐํƒ€ ํ•ญ๋ชฉ์ด ํ•„์š”ํ•œ ๊ฒƒ ๊ฐ™์ง€๋งŒ angular.min.js ์ด ํ‘œ์‹œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

'app': {
            deps: ['libraries/angular', 'libraries/angular-resource', 'libraries/angular-strap']
        },

์œ„์˜ ์Šค๋‹ˆํŽซ์€ shim{} ์š”์†Œ ๋‚ด๋ถ€์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ €์™€ ๋ณ„๋กœ ๊ด€๋ จ์ด ์—†๋Š” ๊ฒƒ ๊ฐ™์•„์„œ ์Šค๋‹ˆํŽซ์—์„œ ์ œ์™ธํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด๊ฒƒ์ด angular.js๊ฐ€ ๋กœ๋“œ๋˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

์•ˆ๋…•,

์˜ˆ, ๊ฐ๋„๊ธฐ๋Š” ํ˜„์žฌ ng-app์„ ์˜ˆ์ƒํ•˜๋ฏ€๋กœ ์ˆ˜๋™์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋œ ์‚ฌ์ดํŠธ์—๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ด ๊ณง ๋ฌธ์„œํ™”๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ ํ™•์ธ thx. ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ฐพ์•˜์œผ๋ฉด ์ด ๋ฌธ์ œ์— ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์‘๋‹ต์ด ๋Š๋ ค์„œ ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

describe('List SMC Charts', function() {
        var ptor= protractor.getInstance();

        beforeEach(function() {
          ptor.driver.get('#/smcChart'); // Just use the plain old webdriver here, it won't complain if Angular isn't loaded yet.
        });

        it('list, no filter', function() {
          var selectOption = ptor.findElement(ptor.By.input("selectOption"));
        });
});

ptor ๋ณ€์ˆ˜๋ฅผ ์–ด๋”˜๊ฐ€์— ์„ค์ •ํ–ˆ์Šต๋‹ˆ๊นŒ? ์˜ˆ: var ptor = protractor.getInstance() ?

์ด์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ์ œ์•ˆํ•œ ์ˆ˜์ • ์‚ฌํ•ญ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค(๊ฐ๋„๊ธฐ@0.10.0, ๊ฐ๋„@1.0.7).

๊ฐ๋„๊ฐ€ ์•„๋‹Œ ๋กœ๊ทธ์ธ ํ™”๋ฉด๊ณผ ์ˆ˜๋™์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋œ ์•ฑ์ด ์žˆ๋Š” requirejs์˜ ์–ด์ƒ‰ํ•œ ์กฐํ•ฉ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

describe('User levels', function() {
    var ptor, driver;

    var findByName = function (name) {
        return driver.findElement(protractor.By.name(name));
    };

    describe('Editors', function() {
        ptor = protractor.getInstance();
        driver = ptor.driver;
        it('Can log in', function() {
            driver.get('http://localhost:8000/');
            findByName('login').sendKeys('test_editor');
            findByName('pass').sendKeys('rubbish');
            driver.findElement(protractor.By.css('button[type="submit"]')).click();
        }, 20000);

        it('Shows the homepage', function() {
            var el = ptor.findElement(protractor.By.css('h1[field="pageTitle"]'));
            expect(el.getText()).toEqual('Welcome to Home');
        }, 30000);

    });
});

๊ทธ๋ฆฌ๊ณ  ๋‚ด main.js :

require([
    'angular',
    'app'
], function (angular, app) {
    'use strict';
    var body = document.body, name = app.name;
    body.setAttribute('ng-app', name);
    body.setAttribute('id', 'ng-app');
    angular.bootstrap(body, [name]);
});

๋กœ๊ทธ์ธ ๋ถ€๋ถ„์ด ์ž‘๋™ํ•˜๊ณ  UnknownError: javascript error: angular is not defined ๋กœ ๊ฐ๋„๊ธฐ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์ „์— ๋ณดํ˜ธ๋œ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ๋˜๋Š” ํ”Œ๋ž˜์‹œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. driver.get('...') ๋ฐ ptor.get('...') ๋กœ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋กœ๋“œํ•˜๋ ค๊ณ  ์‹œ๋„ํ–ˆ์ง€๋งŒ ์•„๋ฌด ๊ฒƒ๋„ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  ํžŒํŠธ/์กฐ์–ธ/์ˆ˜์ • ๊ฐ์‚ฌํžˆ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.

@magnetised ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ๋„ํ•˜๊ธฐ ์ „์— ๋กœ๊ทธ์ธ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋ผ๊ณ  ํ…Œ์ŠคํŠธ์— ์•Œ๋ฆฌ๋Š” ๋ฐฉ๋ฒ•์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. Angular๊ฐ€ ํŽ˜์ด์ง€์— ๋กœ๋“œ๋˜๊ธฐ ์ „์— ptor.findElement๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

'๋กœ๊ทธ์ธ ๊ฐ€๋Šฅ' ์‚ฌ์–‘์€ ์•ฑ์˜ ์ผ๋ถ€ ์กฐ๊ฑด์„ ํ™•์ธํ•˜๋Š” ๋Œ€๊ธฐ ๋ฌธ์œผ๋กœ ๋๋‚˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์˜ˆ์ œ๋ฅผ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค - https://github.com/angular/protractor/blob/master/spec/login/viaConfigConf.js

@juliemr ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค. ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ๋„๊ธฐ๊ฐ€ "๊ฐ๋„๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ"๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ดํ•ดํ–ˆ์ง€๋งŒ ๋ชจ๋“  ๊ฒฝ์šฐ๋ฅผ ๋‹ค๋ฃฐ ์ˆ˜๋Š” ์—†๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋‹ค์‹œ ํ•œ๋ฒˆ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋Š” ์ •ํ™•ํžˆ ๊ฐ™์€ ์„ค์ •์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. @juliemr ์†”๋ฃจ์…˜์„ ๊ณต์œ ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ๋‹น์‹ ์˜ ๋Œ€๊ธฐ ์กฐ๊ฑด์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๊ฐ์‚ฌ ํ•ด์š”!

@jorgenfb ์ € ๋Œ“๊ธ€์ด ์ €๋ฅผ ๊ฒจ๋ƒฅํ•œ ๊ฒƒ์ธ์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค.

๋‚ด ์†”๋ฃจ์…˜์€ ์•ต๊ทค๋Ÿฌ ๋ผ์šฐํ„ฐ๊ฐ€ ๋ถ€ํŒ…๋˜๊ณ  ๋กœ๊ทธ์ธ ์‹œ์Šคํ…œ์ด ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋Š” ๋ฒ ์–ด '/'์—์„œ ๊ธฐ๋ณธ ๊ฒฝ๋กœ(์ด ๊ฒฝ์šฐ '/#view')๋กœ URL์„ ๋ณ€๊ฒฝํ•˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ๊ณผ ๊ด€๋ จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  ํ…Œ์ŠคํŠธ์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

var waitUntilLoaded = function() {
    ptor.wait(function() {
      return ptor.driver.getCurrentUrl().then(function(url) {
        return /view/.test(url);
      });
    });
};

์ด๊ฒƒ์ด ๋‹น์‹ ์—๊ฒŒ ํšจ๊ณผ๊ฐ€ ์žˆ์„์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ ๋ผ์šฐํŒ… ๊ตฌ์„ฑ์— ๋”ฐ๋ผ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

@juliemr ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค, ๋งค๋ ฅ์ฒ˜๋Ÿผ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค :)

Angular๊ฐ€ ๋งค์šฐ ํ‰๋ฒ”ํ•˜๊ฒŒ ๋กœ๋“œ๋˜์–ด ์‹คํ–‰ ์ค‘์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๊ฐ๋„๊ธฐ๊ฐ€ Angular๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋Š” ๋น„์Šทํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค(SO: http://stackoverflow.com/questions/19391813/protractor-fails-to-find-angular). ์‰ฌ์šด ์†”๋ฃจ์…˜(Angular๋ฅผ ์ฐพ์œผ๋ ค๊ณ  ์‹œ๋„ํ•˜์ง€ ์•Š๊ณ  ํ…Œ์ŠคํŠธ)์ด ์šฐ๋ฆฌ๊ฐ€ ๊ฐ€์•ผ ํ•  ๋ฐฉํ–ฅ์ž…๋‹ˆ๊นŒ? Angular ์•ฑ์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด ํŠน๋ณ„ํžˆ ์ œ์ž‘๋œ Protractor์˜ ๋ชฉ์ ์„ ๋ฌด๋„ˆ๋œจ๋ฆฌ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
ํŽธ์ง‘: ๋˜ํ•œ ์ด ๋ฌธ์ œ์—์„œ ์ œ์•ˆ๋œ ์†”๋ฃจ์…˜์ด ์ €์—๊ฒŒ ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค๋Š” ์ ์— ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋” ๋‚˜์€ ๊ฐ๋„์˜ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•ด +1

์ด๊ฒƒ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๊ทธ ์ดํ›„๋กœ ์‹œ๋„๋ฅผ ํฌ๊ธฐํ–ˆ๊ณ  ๋‹ค์‹œ ๋Œ์•„๊ฐ€์„œ ๋ฌด์–ธ๊ฐ€๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•  ์‹œ๊ฐ„์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ์†”์งํžˆ ์ž˜ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ดํ›„๋กœ ๋ฒ„์ „ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•  ๋•Œ ์ด๊ฒƒ์„ ๋‹ซ์œผ๋ ค๋Š” ๊ฒฝ์šฐ ๋…ผ์Ÿํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ์–ธ์ œ๋“ ์ง€ ๋‹ค์‹œ ์—ด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ €๋Š” ์˜ค๋Š˜ ๋ฐฉ๊ธˆ ๊ฐ๋„๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ๊ณ  ์šฐ๋ฆฌ ์•ฑ์€ require.js๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค... ์ˆ˜๋™์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ๊ณผ ๊ด€๋ จํ•˜์—ฌ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ์šฐ์„  ๊ด€์‹ฌ์ด ์žˆ์Šต๋‹ˆ๋‹ค. :-)

๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. Angular ์•ฑ ์™ธ๋ถ€์— ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ „ ์˜๊ฒฌ์˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ์˜ค๋ž˜๋œ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ์ข…๋ฅ˜์˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๋œ ๋ฌธ์„œ๋ฅผ ๋งค์šฐ ์›ํ•ฉ๋‹ˆ๋‹ค.

@hankduan ๋„ค, ๊ทธ๋ ‡๊ฒŒ ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

์ €๋Š” ์ง€๊ธˆ ์ด ๋ฌธ์ œ๋กœ ์–ด๋ ค์›€์„ ๊ฒช๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐฐ๊ฒฝ์€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค. RequireJS๋ฅผ ํ†ตํ•ด ์ข…์†์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๋” ํฐ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์ด ์žˆ์Šต๋‹ˆ๋‹ค.


window.name='NG_DEFER_BOOTSTRAP!'; & angular.resumeBootstrap() ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ ์•ฑ์„ ์—…๋ฐ์ดํŠธํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๋ง์„, ๋‚˜๋Š” ๋‹ค์Œ์— ๋ฌด์—‡์„ํ•ด์•ผํ• ์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค. ์ถ”๊ฐ€ ์กฐ์‚ฌ๋ฅผ ์œ„ํ•ด https://github.com/knalli/protractor-with-deferred-bootstrap ์—์„œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • git clone https://github.com/knalli/protractor-with-deferred-bootstrap.git
  • cd protractor-with-deferred-bootstrap
  • npm install
  • npm run-script webdriver (์…€๋ ˆ๋Š„ ์„œ๋ฒ„)
  • npm run-script server (๋ฏธ๋‹ˆ AJS ์•ฑ์„ ์ œ๊ณตํ•˜๋Š” ๋ฐ๋ชจ ์•ฑ ์„œ๋ฒ„)
  • npm run-script e2e-test (๊ฐ๋„๊ธฐ ์‹คํ–‰...)

๊ฒฐ๊ณผ๋Š”

Using the selenium server at http://localhost:4444/wd/hub
Login: Click
Login: Waiting...
A Jasmine spec timed out. Resetting the WebDriver Control Flow.
The last active task was: 
unknown
F

Failures:

๋’ค์ด์–ด

UnknownError: unknown error: [ng:btstrpd] App Already Bootstrapped with this Element '

๋งˆ์ง€๋ง‰ ์˜ค๋ฅ˜๋Š” ์•ฝ๊ฐ„ ์ด์ƒํ•˜๊ณ  ์˜คํ•ด์˜ ์†Œ์ง€๊ฐ€ ์žˆ๊ธฐ๊นŒ์ง€ ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ์ˆ˜ํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค(์˜ˆ์ƒ์ด ์ •์ƒ์ด๊ณ  ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ํ•œ ์„ฑ๊ณต์ ์ž„).

๊ต์ฐจ ํ™•์ธ: ๋ฐ๋ชจ ์•ฑ์„ ์ˆ˜๋™ ํฌํ•จ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ง€์—ฐ๋˜์ง€ ์•Š์€ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘์œผ๋กœ ์ „ํ™˜ํ•˜๋ฉด(์ด ๋ณ€ํ˜•์€ no-deferred ๋ถ„๊ธฐ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ) ์„ฑ๊ณต์ ์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.


๋‚˜๋Š” ์ด๋ฏธ ์†Œ์œ„ "ํ•ด๊ฒฐ์ฑ…"์„ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. http://www.sebdangerfield.me.uk/2014/01/angularjs-protractor-app-already-bootstrapped-error/
    ๊ด€๋ จ์ด ์—†๊ฑฐ๋‚˜ ์˜คํ•ด์˜ ์†Œ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. --> ์ฐจ์ด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  2. https://github.com/angular/protractor/issues/325#issuecomment -36738048 protractor.getInstance() ๋ฐ ignoreSynchronization ์‚ฌ์šฉ --> ์ฐจ์ด ์—†์Œ
  3. https://github.com/angular/protractor/issues/325#issuecomment -30701683 ํŒจ์น˜ clientSideScripts.js/waitForAngular --> ์ฐจ์ด ์—†์Œ

์–ด์ œ ์ฒซ ๊ฐ๋„๊ธฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๊ณ  knalli์™€ ๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

@knalli ์˜ˆ์ œ๋ฅผ ์ž‘์„ฑํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋ช‡์ผ๋’ค์— ๋ด์•ผ๊ฒ ๋‹ค

๊ทธ๋ž˜์„œ ๋‚ด ๊ฐ๋„๊ธฐ๋Š” ๋ชจ๋“  ์ž‘์—…์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค(ํ•˜๋‚˜์˜ ์‹คํŒจ๋ฅผ ์ œ์™ธํ•˜๊ณ ๋Š” ๋‚ด ์ž˜๋ชป์ž…๋‹ˆ๋‹ค :-P ).
์ด๊ฒƒ์ด ๋„์›€์ด ๋˜๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค.
๋‚ด ์„ค์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๊ฐ๋„๊ธฐ ๋ฒ„์ „:
๋ฒ„์ „ 1.0.0-rc4
์…€๋ ˆ๋Š„ ๋…๋ฆฝ ์‹คํ–‰ํ˜•:
์ตœ์‹ ์ž…๋‹ˆ๋‹ค
ํฌ๋กฌ ๋“œ๋ผ์ด๋ฒ„:
์ตœ์‹ ์ž…๋‹ˆ๋‹ค

ํ™•์‹คํžˆ ๊ด€๋ จ์ด ์žˆ๋Š” ํŒŒ์ผ:

๊ตฌ์„ฑ

/*jshint expr: true*/
/*global browser: true*/
var TEST_DIR =  '../tests/';
exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub'
,  specs : [     '../tests/e2e/_protractor.js'       ]
,  capabilities: {'browserName': 'chrome'    }
,  chromeDriver: './chromedriver'  // <---- I remember not being able to get chromedriver working 
                                                    // for some reason, so I just downloaded the executable and put it
                                                    // in my config directory
,  onPrepare: function() {
    "use strict";
    browser.driver.get('http://localhost:5000/register');
    browser.driver.wait(function() {
      return browser.driver.getCurrentUrl().then(function(url) {
        return /\#\/registration/.test(url);
      });
    });
  }
    //  chromeOnly: true,
    //  baseUrl: 'http://localhost:5000/register'
,  jasmineNodeOpts: {
    showColors: true
  }
};

_๊ฐ๋„๊ธฐ.js

/*jshint expr: true, undef: true */
/* global element: true, by: true, browser: true, model:true */
(function(){
  "use strict";

var RegistrationPage = function(){
  browser.driver.wait(function() {
    return browser.driver.getCurrentUrl().then(function(url) {
      return /\#\/registration/.test(url);
    });
  });
};
  var account = new RegistrationPage();

  account.elem = element.all(by.name('account_exists'));
  account.accountNumber = element(by.name('account_number'));
  account.submitAccountButton = element(by.css('#account_input button'));

  account.selectHasAccount = function(){
    account.elem.get(0).click();
    return browser.getCurrentUrl();
  };

  account.enterAccountNumber = function(keys){

    account.selectHasAccount().then(function(){
      account.accountNumber.sendKeys(keys);
      return account.submitAccountButton.click()
        .then(function(){
          return browser.getCurrentUrl();
      });
      //this wont log because already returned ^
//      console.log('need to return again');
    });
  };
  account.noAccount = function(){
    account.elem.get(1).click();
  };

describe('registration page', function() {
  it("will survive", function(){
    var registrationPage = new RegistrationPage()
    , title = browser.getTitle();
    expect(title).toBe('Web Shop | Homepage');
  });
  it('will change route on account_exists radiobox click', function(){
    var elem = element.all(by.name('account_exists'))
    , _url;
    elem.get(0).click()
    .then(function(){
      _url = browser.getCurrentUrl().then(function(_url){
        expect(_url).toBe('http://localhost:5000/register#/registration/has');
      });
    })
    .then(function(){
        elem.get(1).click().then(function(){
          _url = browser.getCurrentUrl();
          expect(_url).toBe('http://localhost:5000/register#/registration/no');
        });
      });
  });
  describe('navigate the hasAccount path:', function(){
    it('will click hasAccount which will cause a route change.', function () {
      expect(account.selectHasAccount()).toBe('http://localhost:5000/register#/registration/has');
    });
    it("will submit a valid account number", function(){
      expect(account.enterAccountNumber('1231231231')).toBe('http://localhost:5000/register#/user');
    });
  });
  it('will click noAccount which will cause a route change.', function () {
    var  _url;
    account.noAccount();
    _url = browser.getCurrentUrl();
    expect(_url).toBe('http://localhost:5000/register#/registration/no');
  });
});
})();

ํ•„์š”/main.js:

var pathToJQuery
if('querySelector' in document
  && 'localStorage' in window
  && 'addEventListener' in window) {
  //ie9+ and all browsers
  pathToJQuery = 'lib/jquery/dist/jquery'
} else {
  //ie8 and ie7
  pathToJQuery = 'lib/jquery-1.7.2.min'
}
requirejs.config({

      paths : {

//        Paths

          "reg": 'js/registration'
        , "com": 'js/common'
        , "init": 'js/init'
        , "regD": 'js/registration/directives'
        , "regC": 'js/registration/controllers'
        , "regS": 'js/registration/services'
        , "regM": 'js/registration/modules'
//
//        Frameworks
        ,  "angular": "lib/angular/angular"
        ,  "angular.animate" : "lib/angular-animate/angular-animate"
        ,  "jquery": pathToJQuery   //< = == v    long story; don't judge me.
        ,  "jqueryMigrate" : "lib/jquery-migrate/jquery-migrate"
        ,  "mocks": "lib/angular-mocks/angular-mocks"
        ,  "ui_router": "lib/angular-ui-router/release/angular-ui-router"
        ,  "Q": "lib/q/q"

//        Init
        ,  "apps_init" : 'js/init/apps_init'
        ,  "apps_bootstrap" : 'js/init/apps_bootstrap'
//        ,  "jquery_bootstrap" : 'js/init/jquery_bootstrap'
        ,  "registration": "js/init/registration"

//       Common

//         Registration
        ,  "registration": "js/registration/registration"
//         Controllers
        ,  "account.controller" : 'js/registration/controllers/account.controller'
        ,  "user.controller" : 'js/registration/controllers/user.controller'
//         Directives
        ,  "account.directive" : 'js/registration/directives/account.directive'
//    Services
        , "user.service" : "js/registration/services/user.service"
    }
,     shim: {
        'angular': {
            exports : 'angular'
        }
    ,   'angular.animate' : {deps: ['angular']}
    ,   'jqueryMigrate' : {deps: ['jquery']}
    ,   'ui_router' : ['angular']
    ,   'mocks': { deps: ['angular'], 'exports': 'angular.mock' }
}
});

if( pathToJQuery ===  'lib/jquery/dist/jquery' ){
  requirejs([
        'angular' , 'jquery' , 'jqueryMigrate', 'init/jquery_bootstrap', 'init/apps_bootstrap' ]
      , function(angular, jquery, jquery_bootstrap, apps_bootstrap) {
      }
  );
} else {
  requirejs([
      'angular' , 'jquery' , 'jqueryMigrate', 'init/jquery_bootstrap', 'init/apps_bootstrap' ]
    , function(angular, jquery, jqueryMigrate, jquery_bootstrap, apps_bootstrap) {
    }
  );
}
console.log("working");

์ž ์žฌ์ ์œผ๋กœ ์œ ์šฉํ•œ ํŒŒ์ผ:

๊ธฐ๋ณธ ๋ณด๊ธฐ(ํ•ธ๋“ค๋ฐ”)

<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org"
            xmlns:fab="http://angularjs.org"
            xmlns:ui ="http://angularjs.org"
            lang = "en" class = "no-js" >
<head >
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
<meta charset="utf-8">
<link rel="shortcut icon" type="image/x-icon" media="all" href="/images/favicon.ico" />
<script type="text/javascript" src="lib/modernizr.js"></script>

<!-- Default ie theme files:-->
<!--[if lte IE 9]><script type="text/javascript">console = {}; console.log = function(){};</script><![endif]-->
<!--[if IE 8]><link type="text/css" rel="stylesheet" href="build/css/ie8/theme.css" /><![endif]-->

<!--[if IE 7]><link type="text/css" rel="stylesheet" href="build/css/ie7/theme.css" /><![endif]-->

<!-- Normal styles -->
<link type="text/css" href="css/style.css" rel="stylesheet">


<!--[if IE 8]> <link type="text/css" rel="stylesheet" href="build/css/ie8/ie_8.css"  /> <![endif]-->
<!--[if IE 7]> <link type="text/css" rel="stylesheet" href="build/css/ie7/lte_ie7.css"/> <link type="text/css" rel="stylesheet" href="build/css/ie7/ie_7.css" /><![endif]-->


<script data-main="main" src="lib/requirejs/require.js"></script>
</head >

<body class = "page-homepage language-en" >
<div id = "wrapper" >
    <div id = "page" data-currency-iso-code = "USD" >
        {{> _header}}
        <a href = "#skip-to-content" class = "skiptocontent" >Skip to content</a >
        <a href = "#skiptonavigation" class = "skiptonavigation" >Skip to navigation menu</a >
    <!--[if lte IE 10]> <p class="browsehappy" style="margin-top: 10px; ">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
    <main ng-cloak id = "content" role = "main" class = "page_main <!--[if IE7]>ng-cloak<![endif]-->" >

        </main>
    </div >
</div >
</body >

</html >

controller.js(์„œ๋ฒ„ ๊ตฌ์„ฑ)

(function(){
    "use strict";

    //  node requires
    //==============================
    // express-hbs
    // express
    //
    const express = require('express')
        , portN = 5000
        , hbs = require('express-hbs')
        , app = express()
        , path = require('path')
        , Server = require('./server')
        , bodyParser = require('body-parser')
        , serveStatic = require('serve-static')
        , session = require('express-session')

    , router = express.Router()
    , logger  = require('morgan')
        , methodOverride = require('method-override')
        , errorhandler = require('errorhandler')


    //  view paths
    //==============================
    //
    //
    //
        , viewsD = __dirname + '/views/'
        , partialsD = viewsD + 'partials/'
        , layoutsD = viewsD + 'layouts/'
        , testsD =  __dirname + '/tests/'
    , publicD = __dirname + '/public/'
    , styleguideD = publicD+ '/styleguide/'
        , defaultF = layoutsD + 'default.hbs';
    //  Express setup.
    //==============================
    //
    //
    //
    app.use(express.static(publicD))
            .use(bodyParser())
            .use(logger('dev'))
            .use(methodOverride())
            .use(serveStatic(path.join(__dirname, 'public')))
            .use(errorhandler());

    app.set('view engine', 'hbs')
            .set('port', process.env.PORT || portN)
            .set('cache', false)
            .set('views', viewsD);

    app.engine('hbs', hbs.express3({
        partialsDir: partialsD,
        defaultLayout: defaultF,
        layoutsDir: layoutsD
    }));

    var indx = {};

    app.get( '/' ,function (req, res) {
        res.render(partialsD + '_home.hbs', indx);
    });

    app.get( '/register' ,function (req, res) {
        res.render(partialsD + '_register.hbs', indx);
    });

    new Server(app);

})();

์„œ๋ฒ„.js

module.exports = function(app){
  "use strict";
    var http = require('http');
    http.createServer(app).listen(app.get('port'), function(){
        console.log('Express server listening on port ' + app.get('port'));
    });
};


module.exports = function (app) {
    "use strict";
    var http = require ('http');
    http.createServer (app).listen (app.get ('port'),function () {
        console.log ('Express server listening on port ' + app.get ('port'));
    }).on ('error', function (e) {
        console.log ("got error: " + e.message);
    });
};

package.json(๊ด€๋ จ ๋ฐœ์ทŒ๋ฌธ)

"dependencies": {
    "body-parser": "^1.2.1",
    "bower": "^1.3.2",
    "errorhandler": "^1.0.1",
    "express": "^4.3.1",
    "express-hbs": "^0.7.9",
    "express-session": "^1.2.0",
    "node-dev": "^2.3.0"
  },

  "scripts": {
    "start": "node --harmony $* ./node_modules/.bin/node-dev controller.js",
  },
"devDependencies": {
  "browserstack-webdriver": "^2.41.1",
  "grunt-protractor-runner": "^1.0.0",
  "method-override": "^1.0.2",
    "morgan": "^1.1.1",
    "protractor": "^0.23.1",
    "requirejs": "^2.1.11",
    "serve-static": "^1.1.0",
},
  "author": "Andrew Luhring"

ํŒŒ์ผ ๊ตฌ์กฐ:

config/
   protractor.config.js
    chromedriver

server.js
controller.js

public/
   main.js
   js/
   lib/

tests/
   e2e/
      _protractor.js    <=== test file.

views/
  layouts/
      default.hbs

  partials/
      _head.hbs
      _register.hbs   <=== file being tested.

๊ทธ๊ฒƒ์ด ๋‚˜์˜ ์ „์ฒด ์„ค์ •์ด๋ผ๊ณ  ํ™•์‹ ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์žฅํ™ฉํ•˜๊ฒŒ ๊ฐˆ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ํ•ด์•ผ ํ•  ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ํ…Œ์ŠคํŠธ ๊ด€๋ จ ๋ถ€๋ถ„์„ URL์— ์ถ”๊ฐ€ํ•˜์—ฌ ๋‚ด ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ฝ”๋“œ๊ฐ€ '์ •์ƒ์ ์œผ๋กœ' ์‹คํ–‰ ์ค‘์ธ์ง€ ์•„๋‹ˆ๋ฉด ๊ฐ๋„๊ธฐ ํ…Œ์ŠคํŠธ๋กœ ์‹คํ–‰ ์ค‘์ธ์ง€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก ํ•œ ๋‹ค์Œ ํ…Œ์ŠคํŠธ์šฉ์œผ๋กœ๋งŒ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  2. document.ready์—์„œ angular.bootstrap ํ˜ธ์ถœ
  3. ์ฐฝ ์ด๋ฆ„์„ 'NG_DEFER_BOOTSTRAP!'์œผ๋กœ ์‹œ์ž‘ํ•˜๋„๋ก ์„ค์ •ํ•˜์‹ญ์‹œ์˜ค. angular.bootstrap์„ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์—
  4. ๋‚ด ์ตœ์ƒ์œ„ ์š”์†Œ์— 'ng-app' ์†์„ฑ ์„ค์ •
  5. angular๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋„๋ก window.angular๋ฅผ ์„ค์ •ํ•˜์‹ญ์‹œ์˜ค.

https://github.com/mikaraento/protractor-reuse/blob/master/index.js ์ฐธ์กฐ

ํฌ๋กฌ ๋ฐ ํŒŒ์ด์–ดํญ์Šค ๋ธŒ๋ผ์šฐ์ €์—์„œ ์™„๋ฒฝํ•˜๊ฒŒ ์‹คํ–‰๋˜๋Š” ๊ฐ๋„๊ธฐ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ Internet Explorer์—์„œ ํ•ญ์ƒ ์‹คํ–‰ํ•  ์ˆ˜ ์—†๋Š” ์ด์œ ๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ๋™์ผํ•œ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์™„๋ฒฝํ•˜๊ฒŒ ์‹คํ–‰๋˜์ง€๋งŒ Internet Explorer์—์„œ๋Š” ํ•ญ์ƒ ๋™์ผํ•˜๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ๊ฐ„ ์ดˆ๊ณผ ์˜ˆ์™ธ์™€ ๊ฐ™์€ ์ž์ฃผ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
๋ฉ”์‹œ์ง€: '์‹œ๊ฐ„ ์ดˆ๊ณผ: ์‚ฌ์–‘์ด ์™„๋ฃŒ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” 30000๋ฐ€๋ฆฌ์ดˆ ํ›„์— ์‹œ๊ฐ„ ์ดˆ๊ณผ๋จ',
์ถ”์ : { ์Šคํƒ: ์ •์˜๋˜์ง€ ์•Š์Œ } } ]
๋˜ํ•œ Internet Explorer์—์„œ ์š”์†Œ๊ฐ€ ๊ฐ์ง€๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์„ defaultTimeou tInterval:1000000 ์œผ๋กœ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ๋‚ด ์Šคํฌ๋ฆฝํŠธ์—์„œ Internet Explorer์—์„œ ์‹คํ–‰ํ•˜๋Š” ๋™์•ˆ ์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค...

์•„๋ฌด๋„ ์ด๊ฒƒ์— ๋Œ€ํ•ด ๋‚˜๋ฅผ ๋„์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

๋ฌธ์•ˆ ์ธ์‚ฌ,
์‚ฐํ† ์‹œ ์ถ˜์ถ”

์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘์—๋งŒ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์ง€ ์•Š์Šต๋‹ˆ๊นŒ? ์•„๋‹ˆ๋ฉด ์ด๋ฏธ Chrome ๋ฐ Firefox์—์„œ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋œ ์•ฑ์„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

@hankduan ์ด๋ฏธ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์‚ดํŽด๋ณด์…จ๋‚˜์š”? ์šฐ๋ฆฌ๊ฐ€ ๋„์šธ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๊นŒ?

๋‚˜๋Š” ๊ฐ๋„๊ธฐ(https://github.com/angular/protractor/pull/1155)์—์„œ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘์ด ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์„ ๋ณ€๊ฒฝํ•˜๋Š” CL์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฃผ์š” ๋ชฉ์ ์€ ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ ๋ฌธ์ œ๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์ด์ง€๋งŒ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ๋ฐฉ์‹์— ๋”ฐ๋ผ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์œผ๋กœ ์ˆ˜์ •(๋˜๋Š” ์ตœ์†Œํ•œ ๋„์›€)๋˜์–ด์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์„ค์ •ํ•œ ์ ์ด ์—†์œผ๋ฏ€๋กœ ์ด๊ฒƒ์€ ์ด๋ก ์ ์ธ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด PR์€ ๊ฐ๋„๊ธฐ ๋ณ€๊ฒฝ๊ณผ Angular ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ๊ธฐ๊ฐ„์— ๊ณต๊ฐœ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๋Š” Protractor์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด Angular๋ฅผ ์ˆ˜๋™์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ์„ ๋•Œ ๊ฐ€์žฅ ๋งŽ์ด ๊ฒ€์ƒ‰๋œ ๊ฒฐ๊ณผ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค๋ฅธ ์†”๋ฃจ์…˜์„ ์ œ์•ˆํ•˜๊ธฐ์— ์—ฌ๊ธฐ๋งŒํผ ์ข‹์€ ๊ณณ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ „์ฒด ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ Angular ์•ฑ์ธ ๊ฒฝ์šฐ์— ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค(์œ„์˜ ์˜ˆ์™€ ๊ฐ™์ด ๋กœ๊ทธ์ธ ํ›„ div์—์„œ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘๊ณผ ๊ฐ™์€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋Œ€์‹ ).

Angular๊ฐ€ ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๊ฐ๋„๊ธฐ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์—ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ณต์‹ Angular ๋ฌธ์„œ์—์„œ๋Š” ๊ฐ๋„๊ธฐ๋กœ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ๋ช…์‹œํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๊ณต์‹ ๋ฌธ์„œ๊ฐ€ ๊ธˆ์ง€ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ํ”ผํ•˜๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚ด ์†”๋ฃจ์…˜

์ €๋Š” Grunt๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‚ด ์ฝ”๋“œ๋ฅผ ๋‹จ์ผ JS ํŒŒ์ผ๋กœ ์—ฐ๊ฒฐ/Uglifyํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ์€ index.html์—์„œ ์ฐธ์กฐํ•˜๋Š” ์œ ์ผํ•œ ์Šคํฌ๋ฆฝํŠธ์ด๋ฉฐ ์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. script.js๋ผ๊ณ  ํ•ฉ์‹œ๋‹ค. ๊ฐœ๋ฐœ์—์„œ๋Š” ๋‹ค๋ฅธ Grunt ์ž‘์—…์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ์บ์‹ฑ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํƒ€์ž„์Šคํƒฌํ”„์™€ ํ•จ๊ป˜ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋น„๋™๊ธฐ์‹์œผ๋กœ ๋‚˜๋จธ์ง€ JS ํŒŒ์ผ์„ ๋กœ๋“œํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ํŒŒ์ผ๋งŒ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ด ์—ฐ๊ฒฐ๋œ ์Šคํฌ๋ฆฝํŠธ ์„ธํŠธ๋Š” ๋‚ด๊ฐ€ ๊ฐœ๋ฐœํ•˜๋Š” ๋™์•ˆ script.js๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. ๋‘ ์‹œ๋‚˜๋ฆฌ์˜ค ๋ชจ๋‘ index.html์—์„œ ์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ์™€ ํ•จ๊ป˜ script.js๋งŒ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ

๋”ฐ๋ผ์„œ ๊ฐœ๋ฐœ์—์„œ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ JS์— ์—ฐ๊ฒฐํ•˜์ง€ ์•Š์€ ๋‹จ์ผ ํŒŒ์ผ์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์‰ฝ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ชจ๋“  JS๊ฐ€ ๋กœ๋“œ๋œ ํ›„์— ์ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋กœ๋“œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

bootstrap_dev.js

        .element(document.body)
        .ready(function() {
            angular.bootstrap(document.body, ['App']);
        });

์ƒ์‚ฐ / ๊ฐ๋„๊ธฐ

ํ”„๋กœ๋•์…˜์—์„œ๋Š” ๊ฐ๋„๊ธฐ๊ฐ€ ๋งˆ์Œ์— ๋“ค์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜๋™์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋‚ด ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— ๋Œ€ํ•ด ๊ฐ๋„๊ธฐ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ HTML์—์„œ "ng-app" ์†์„ฑ์„ ์ œ๊ฑฐํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด Development์—์„œ ์กฐ๊ธฐ์— ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๋˜์ง€ ์•Š๋„๋ก ํ•˜์—ฌ ์ผ๋ฐ˜์ ์ธ ์ž๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ €๋Š” script.js์—์„œ ๋‹ค๋ฅธ ๋ชจ๋“  JS๋ณด๋‹ค ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋จผ์ € ๋ฐฐ์น˜ํ•˜์—ฌ ํ”„๋กœ๋•์…˜์—์„œ ์ด๋ฅผ ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค.

bootstrap_prod.js

    window.document.body.setAttribute("ng-app", "myApp");

"myApp"์„ ๊ธฐ๋ณธ ๋ชจ๋“ˆ์˜ ์ด๋ฆ„์œผ๋กœ ๋ฐ”๊พธ์‹ญ์‹œ์˜ค.

๊ฒฐ๋ก 

๊ฐœ๋ฐœ์—์„œ ์œ„์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋กœ๋“œํ•˜๊ณ  ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— ๋Œ€ํ•ด์„œ๋งŒ ๊ฐ๋„๊ธฐ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ๊ดœ์ฐฎ๋‹ค๋ฉด(์Šคํฌ๋ฆฝํŠธ ํƒœ๊ทธ๋ฅผ ํ†ตํ•ด ๋‹จ์ผ ํŒŒ์ผ์—์„œ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Angular๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „์— ng-app ์ง€์‹œ๋ฌธ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ž๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์ด ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด์ œ ๊ฐœ๋ฐœ์—์„œ ๋‹ค์–‘ํ•œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋น„๋™๊ธฐ์‹์œผ๋กœ ๋กœ๋“œํ•˜๊ณ  ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์— ๋Œ€ํ•ด ๊ฐ๋„๊ธฐ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ•˜๊ฒŒ ๋œ ๊ฒƒ์€ ๋‚ด protractor.conf.js์— ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด์—ˆ์Šต๋‹ˆ๋‹ค.

onPrepare: function() 
    {
        // implicit and page load timeouts
        browser.manage().timeouts().pageLoadTimeout(40000);
        browser.manage().timeouts().implicitlyWait(25000);

        // for non-angular page
        browser.ignoreSynchronization = true;

        // sign in before all tests
     }

๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๋ฉด ์•Œ๋ ค์ฃผ์„ธ์š” =]

๋น„์Šทํ•œ ๋ฌธ์ œ๊ฐ€์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ์œ ์šฉํ•œ ์ •๋ณด๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.

๋ถ€ํŒ… ํ”„๋กœ์„ธ์Šค

์•ฑ์ด ํ”„๋กœ๋•์…˜์—์„œ ๋ถ€์ŠคํŠธ๋žฉ๋˜๋Š” ๋ฐฉ์‹์—๋Š” ์ฐจ์ด๊ฐ€ ์—†์ง€๋งŒ ํ…Œ์ŠคํŠธ์—์„œ ๊ฐ๋„๊ธฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

  1. ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ๊ฐ๋„๊ฐ€ ์ผ์‹œ ์ค‘์ง€๋˜๋„๋ก window.name์„ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค. NG_DEFER_BOOTSTRAP! (์ด๊ฒƒ์€ ๊ฐ๋„ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค!)
  2. ๋ชจ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ๋‹ค์Œ angular.resumeBoostrap()์„ ํ˜ธ์ถœํ•˜๋ฉด ์•ฑ์ด ์‹ค์ œ๋กœ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๊ฐ€ ์ด ํ”„๋กœ์„ธ์Šค์™€ ์ถฉ๋Œํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. angular๋Š” ํ•œ ๋ฒˆ๋งŒ ๋ถ€์ŠคํŠธ๋žฉ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. angular.boostrap() ๋˜๋Š” angular.resumeBoostrap()์— ๋Œ€ํ•œ ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ์€ ๊ฐ๋„๊ธฐ ๋˜๋Š” ์•ฑ์„ ์†์ƒ์‹œํ‚ฌ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ณธ๋ฌธ์— ng-app ์ด ์ „ํ˜€ ํ•„์š”ํ•˜์ง€ ์•Š์œผ๋ฉฐ angular.boostrap()์„ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ NG_DEFER_BOOTSTRAP์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ๋„๊ธฐ์— ์˜ํ•ด ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค! ์ œ๋Œ€๋กœ.

์ฐฝ ์ด๋ฆ„

๋กœ๋”ฉ ๋ฐ ๋ถ€ํŒ… ์ค‘์— window.name ์ด ์•ฑ ์ฝ”๋“œ์— ์˜ํ•ด ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•˜์‹ญ์‹œ์˜ค. ์ œ ๊ฒฝ์šฐ์—๋Š” jquery ๊ฐ€ ๊ทธ๋ ‡๊ฒŒํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ์ด์œ ๋ฅผ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ jquery ๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๊ณ  ํ…Œ์ŠคํŠธ๊ฐ€ ์ž‘๋™ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๋ฌธ์ œ์˜ ๊ทผ์›์ด๋‹ค!

์ง€๊ธˆ์€ ๋‚ด ํ”„๋กœ์ ํŠธ์— ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ๋ฅผ ์ค‘๋‹จํ•˜์ง€ ์•Š๊ณ  jquery๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค!

๋“œ๋””์–ด,

์šฐ๋ฆฌ์˜ ๋ฌธ์ œ๋Š” ํ•œ ํŽ˜์ด์ง€์— ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฒ„์ „์˜ jQuery์— ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค(๋‘ ๋ฒˆ์งธ ๋ฒ„์ „์€ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์˜ํ•ด ๋กœ๋“œ๋จ). ๊ทธ๊ฒƒ์„ ์ œ๊ฑฐํ•˜๊ณ  ํŽ˜์ด์ง€์— jQuery๋งŒ ๋‚จ๊ฒจ๋‘๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์ผ๋ถ€ ์ฝ”๋“œ๊ฐ€ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

index.html

<body>
...
<script data-main="require.conf" type="text/javascript" src="bower_components/requirejs/require.js"></script>
</body>

requirejs ๊ตฌ์„ฑ

...
 deps: ["proto/main"],
...

ํ”„๋กœํ† .js

// We just load deps and register the module:
 define(["deps"],function(){
   angular.module('proto'...
 }

ํ”„๋กœํ† /๋ฉ”์ธ.js

define(["require", "exports", "proto", "angular"], 
    function (require, exports) {

    angular.bootstrap(document, ['proto']);
});

๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ๋กœ ํ…Œ์ŠคํŠธ ์‚ฌ์–‘

var protractor = require('protractor');
var ptor;
function prepareBrowser() {

    ptor = protractor.getInstance();

    return ptor.get(ptor["baseUrl"] + '/login').then(function () {
        console.log("got");       
        return ptor.waitForAngular().then(function () {
            console.log("ngwaited");
        });
    });
}
describe('container', function () {
    beforeEach(function () {
        return prepareBrowser();
    });
    describe('form tests', function () {
        it('form is shown', function () {
            expect($('[ng-controller=LoginContr]').isDisplayed()).toBeTruthy();
        });

    });
});

์‚ฐ์ถœ

Using the selenium server at http://localhost:4444/wd/hub
got
ngwaited
container
  form tests
    form is shown - pass


Finished in 3.112 seconds
1 test, 1 assertion, 0 failures

๋งŒ์„ธ!

ng-app ์—์„œ angular.bootstrap() ๋กœ ์ „ํ™˜ํ–ˆ์„ ๋•Œ projotractor.conf ์— rootElement: 'div' ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ๋„๊ธฐ๋ฅผ ๋‹ค์‹œ ์ž‘๋™์‹œํ‚ค๋Š” ๋ฐ ํ•„์š”ํ•œ ์œ ์ผํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ๊ฐ๋„ 1.3.0๊ณผ ๊ฐ๋„๊ธฐ 1.3.1์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

@jongunnip ๋„ต ์ €๋„ ์ž˜ ๋ดค์Šต๋‹ˆ๋‹ค ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

@mikaraento ๋•๋ถ„์— ์ž˜ ๋˜์—ˆ์–ด์š”~

์•ˆ๋…•ํ•˜์„ธ์š” ์—ฌ๋Ÿฌ๋ถ„, ์ด ์Šค๋ ˆ๋“œ์™€ ๊ด€๋ จ๋œ ์ด์ƒํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋‚˜๋Š” ์ผ๋ฐ˜ ๊ฐ๋„ ์•ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. requirejs๋„ ์—†๊ณ , ๊ฐ๋„๊ธฐ 2.0.0์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ์—์„œ ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
org.openqa.selenium.WebDriverException: unknown error: [ng:btstrpd] App Already Bootstrapped with this Element ... ๋ฉ”์‹œ์ง€

grunt:server ๋Œ€์‹  ์ •์  ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ๊ณตํ•˜๋„๋ก Nginx ์„œ๋ฒ„๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ์ค‘์— livereload๋กœ ์‹คํ–‰ํ•ด๋„ ์ƒ๊ด€์—†๋‚˜์š”?

์ •์  ์„œ๋ฒ„๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ์ž‘๋™ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ณ  grunt๋Š” ์ผ๋ถ€ 404 ๋˜๋Š” ๋ฆฌ๋””๋ ‰์…˜์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ ์›น ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ๋ฏธ์ณ๊ฐ‘๋‹ˆ๋‹ค.

์•ˆ๋…•ํ•˜์„ธ์š” ์—ฌ๋Ÿฌ๋ถ„,

๋งŽ์€ ์‹œ๊ฐ„์„ ์ฝ๊ณ  ์ฝ์€ ํ›„ ๋‚ด ํ”„๋กœ์ ํŠธ์— ์ž˜ ๋งž๋Š” ํ•˜๋‚˜์˜ ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค( ๊ฐ๋„๊ธฐ ์‹คํ–‰ ํ๋ฆ„ ์ˆ˜์ • ).

  1. ๊ฐ๋„๊ธฐ์— ์•ฑ์„ ์—ด ๋•Œ URL์— ?protractor-test ์ถ”๊ฐ€( ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ )
  2. ๊ฐ๋„ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค( ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ ).
if(location.href.indexOf("protractor-test") < 0){
  // start angular app
  angular.bootstrap(document, [module.name]);
} else {
  // start angular app to protractor tests
  window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
  angular.bootstrap(document, [module.name]);
}

๊ทธ๋ฆฌ๊ณ  ๋งˆ์นจ๋‚ด ๋ชจ๋‘ ๋‹ค์‹œ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค \o/

@juliemr ์‚ฌ๋ณธ๊ณผ ํ•จ๊ป˜

์ €๋Š” ์ตœ๊ทผ(๋‹จ 1-2์ฃผ) ๊ฐ๋„๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Angular ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‚ด ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์—๋Š” ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์˜ ๋ณธ๋ฌธ ํƒœ๊ทธ์— ng-App์ด ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด ํ…Œ์ŠคํŠธ๋Š” "Angular๋ฅผ ํŽ˜์ด์ง€ httP://(WebSite Address)์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๊ฐ€ ํ™•์‹คํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ(๋ธŒ๋ผ์šฐ์ € ignoresync ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ผœ๋ฉด ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€๋งŒ ์ด๊ฒƒ์€ ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•์ด ์•„๋‹™๋‹ˆ๋‹ค), Juliemr์ด ์ œ์•ˆํ•œ ๊ฒƒ์„ ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ(๋ ˆ๊ฑฐ์‹œ ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒ์ƒ‰), ๋‚ด ์•ฑ์ด ๋ถˆํ‰ํ•˜๋Š” ์ด์œ ๋ฅผ ์ดํ•ดํ•˜๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. body ํƒœ๊ทธ์— ng-App์ด ์žˆ์–ด๋„. ์•ฑ ๊ธฐ๋Šฅ ๋™์ž‘์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€.. ์ผ๋‹จ ๋กœ๊ทธ์ธํ•˜๋ฉด ํ™ˆ ํŽ˜์ด์ง€์˜ ์ผ๋ถ€ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด ๋‹ค๋ฅธ ์ฐฝ/ํƒญ์ด ์—ด๋ฆฝ๋‹ˆ๋‹ค. ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์ด ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ๋‹จ๊ณ„๋ฅผ ์ฐพ๋Š” ๋ฐ ๋„์›€์ด ๋ ๊นŒ์š”? bootstrap.js ํŒŒ์ผ์—์„œ ๋ฌด์–ธ๊ฐ€๋ฅผ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๊นŒ(bootstrap ํŒŒ์ผ์— ng-app ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํ‘œ์‹œ๋˜์ง€ ์•Š์•„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ? ng-app์ด ์—†๋Š” ๊ฐ๋„ ์•ฑ์—์„œ ์ž‘๋™ํ•˜๋„๋ก ๊ฐ๋„๊ธฐ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ชจ๋ฒ” ์‚ฌ๋ก€ ๋˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? @erkobridee ๊ท€ํ•˜์˜ ์˜ˆ๋ฅผ ๋” ์ž์„ธํžˆ ์„ค๋ช…ํ•ด ์ฃผ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ๊ทธ๋ฆฌ๊ณ  ๊ฐ๋„๊ธฐ๊ฐ€ ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ ๊ฐ๋„๊ธฐ๊ฐ€ ๊ธฐ๋‹ค๋ ค์•ผ ํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ๊ด€๋ จํ•˜์—ฌ window.name์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

@juliemr Angular 1.5์—์„œ๋Š” ์ˆ˜๋™์œผ๋กœ ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•˜๋Š” ๊ฒƒ์ด ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์— ์„ ํ˜ธ๋˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋„์ค‘์— ์ถ•๋ณต๋ฐ›์€ ๊ฐ๋„๊ธฐ ์ง€์›์ด ์žˆ์Šต๋‹ˆ๊นŒ?

+1 ์ด ๋ฌธ์ œ๊ฐ€ ๋” ๋†’์€ ์šฐ์„  ์ˆœ์œ„๋ฅผ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๊ฐ๋„๊ธฐ๊ฐ€ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ๊ณผ ํ•จ๊ป˜ ์ž‘๋™ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฐ๋„๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์ ์ด ์—†์Šต๋‹ˆ๋‹ค. Webdriver๋ฅผ ํ†ตํ•ด ๊ฐ๋„๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์š”์ ์€ ๊ฐ๋„๊ธฐ๊ฐ€ ๋ชจ๋“  ๊ฐ๋„ ๋™๊ธฐํ™”๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ฐ™์€ ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์—…๋ฐ์ดํŠธ?

์•ˆ๋…•ํ•˜์„ธ์š”, ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๊ณ„ํš์ด๋‚˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์ด์ƒํ•˜๊ฒŒ ํ”ผ๋“œ๋ฐฑ์ด ์—†์Šต๋‹ˆ๋‹ค. ng-app์ด ๋ˆ„๋ฝ๋œ ์•ฑ์ด ์žˆ๊ณ  ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
๊ฐ๋„๊ธฐ๋กœ ๊ทธ๋“ค์„

2016๋…„ 7์›” 16์ผ ์˜ค์ „ 5์‹œ 1๋ถ„์— "millerwx" ์•Œ๋ฆผ @github.com์ด ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

์•ˆ๋…•ํ•˜์„ธ์š”, ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•œ ๊ณ„ํš์ด๋‚˜ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

โ€”
๋‹น์‹ ์ด ๋Œ“๊ธ€์„ ๋‹ฌ์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์„ ๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
์ด ์ด๋ฉ”์ผ์— ์ง์ ‘ ๋‹ต์žฅํ•˜๊ณ  GitHub์—์„œ ํ™•์ธํ•˜์„ธ์š”.
https://github.com/angular/protractor/issues/66#issuecomment -233104701,
๋˜๋Š” ์Šค๋ ˆ๋“œ ์Œ์†Œ๊ฑฐ
https://github.com/notifications/unsubscribe-auth/AOghFs2ff0e-88DLafNGD3iCH8uCOgVaks5qWEmIgaJpZM4A82d7
.

2016๋…„ ํ˜„์žฌ๊นŒ์ง€ ์ด๊ฒƒ์€ ์—ฌ์ „ํžˆ โ€‹โ€‹๋‚˜์—๊ฒŒ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Magnetized ๋‹ต๋ณ€์„ ๋”ฐ๋ž์Šต๋‹ˆ๋‹ค. ๋‚˜๋Š” ๋‹จ์ง€ ์•ฝ๊ฐ„์˜ ๋ณ€ํ™”๋ฅผ ์ฃผ์—ˆ๋‹ค. ๋Œ€ํ™”์—์„œ ํ›Œ๋ฅญํ•˜๊ณ  ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ ๊ฒƒ์€ ๋ฆฌ๋””๋ ‰์…˜์„ ์ถ”์ ํ•˜๊ณ  ์•ฑ ๋‚ด๋ถ€๋กœ ๋‹ค์‹œ ์ด๋™ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. *์ €๋Š” Babel๋กœ ํ–ˆ๋Š”๋ฐ ES5๋กœ ์“ฐ์…”๋„ ๋ฉ๋‹ˆ๋‹ค.

`describe('๋กœ๊ทธ์ธ ํ™”๋ฉด', ()=>{

var urlTimeout = 9000;

const waitForUrlRegex = (regex, urlMatcher )=>{

    return driver.wait(()=>{
        return driver.getCurrentUrl().then( (url)=>{
            return regex.test(url);
        });
    }, urlTimeout, "Expectation error: Timeout for url not matching " + urlMatcher );
};

const waitForRoute = (route)=>{
    return waitForUrlRegex( new RegExp( route + "$" ), route  );
}

const waitForUrl = (url)=>{
    return waitForUrlRegex( new RegExp( "^https?:\/\/" + url ), url );
}

it('should go to homepage and click login button', ()=>{

    driver.get( app_url, urlTimeout);

    //there is a redirect to welcome route
    expect( waitForRoute("welcome") ).toBe(true);

    const loginButton = element.all(by.className("login_button")).get(0);
    loginButton.click();
});

it( 'should enter credentials', ()=>{
    expect( waitForUrl( non_angular_login_url ) ).toBe(true);
    driver.findElement( by.name("user")).sendKeys( "username");
    driver.findElement( by.name("password")).sendKeys( "userpassword");
    driver.findElement( by.tagName("button")).click();
});

it( 'should go back to app, and continue testing', ()=>{
    expect( waitForRoute( "user-screen") ).toBe(true);
});

});`

config.on์ค€๋น„

onPrepare: ()=>{ require("babel-register"); global.driver = browser.driver; }

๋ฆฌ๋””๋ ‰์…˜์„ ์กฐ์‚ฌํ•˜๊ณ  ์‹ถ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์ž ์ž๊ธฐ ์ƒํƒœ๋กœ ๋‘” ๋‹ค์Œ ๊ฐ๋„๊ธฐ๋กœ ์ž‘์—…์„ ์‹œ์ž‘ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

`driver.get( app_url+ "/#/ํ™˜์˜", urlTimeout);

    driver.sleep( 1000 );
    const loginButton = element.all(by.className("login_button")).get(0);
    loginButton.click();`

์šฐ๋ฆฌ๋Š” ์‹ค์ œ๋กœ ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ •๋ณด๋Š” ์˜ค๋ž˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. https://github.com/angular/protractor/issues/3857 ๋„ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘์ด ํ›จ์”ฌ ๋” ์•ˆ์ •์ ์ด ๋ฉ๋‹ˆ๋‹ค.

@sjelin ์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์ง€์›ํ•˜๋Š” ๋ฌธ์„œํ™”๋œ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ? ๊ฐ๋„๊ธฐ 5.0์— ๋Œ€ํ•œ ๊ด€๋ จ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ˆ˜๋™ ๋ถ€ํŠธ์ŠคํŠธ๋ž˜ํ•‘ ์ค‘์— ๋™์ผํ•œ ๋ฌธ์ œ์— ์ง๋ฉดํ–ˆ์Šต๋‹ˆ๋‹ค. grunt์™€ ํ•จ๊ป˜ ์‹คํ–‰ํ•  ์†”๋ฃจ์…˜์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.
๊ฐ๋„๊ธฐ ๋ฒ„์ „ 2.5.1์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. grunt ํŒŒ์ผ์—์„œ ์ด๊ฒƒ์„ ์–ธ๊ธ‰ํ•˜์‹ญ์‹œ์˜ค.

grunt.initConfig({ protractor: { options: { configFile: "protractor.conf.js", // Default protractor config file keepAlive: true, // If false, the grunt process stops when the test fails. noColor: false, // If true, protractor will not use colors in its output. args: { // Arguments passed to the command } }, e2e: { options: { // Stops Grunt process if a test fails keepAlive: true } } } })

  1. npm install grunt-protractor-runner --save-dev ์„ค์น˜ ๋ฐ ๋กœ๋“œ ์ž‘์—… grunt.loadNpmTasks('grunt-protractor-runner') ์ถ”๊ฐ€

  2. ๋‹ค์Œ ๋ช…๋ น์œผ๋กœ ๊ฐ๋„๊ธฐ๋ฅผ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค.
    grunt protractor
    ๋˜๋Š” ์ œํ’ˆ๊ตฐ ์ด๋ฆ„์œผ๋กœ
    grunt protractor --suite suite_name

๊ทธ๊ฒƒ์ด ๋‹น์‹ ์„ ์œ„ํ•ด ์ž‘๋™ํ•˜๋Š”์ง€ ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค.

์ด๊ฒƒ์€ ๋‚˜๋ฅผ ์œ„ํ•ด ์ผํ–ˆ์Šต๋‹ˆ๋‹ค.
๋ธŒ๋ผ์šฐ์ €.ignoreSynchronization = true;
๋ธŒ๋ผ์šฐ์ €.get(URL);
๋ธŒ๋ผ์šฐ์ €.ignoreSynchronization = ๊ฑฐ์ง“;

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰