codeceptjs学习笔记 - Page Object设计模式

本文章基于codeceptjs@1.0.1

这篇文章会介绍UI测试最常用的设计模式在codeceptjs的应用 - Page Object

Benefit

Page Object有哪些好处?

  • 代码重用。可以在多个测试场景里面重用代码,减少重复的代码
  • 易维护。如果系统有变动,那么只需要在一个地方进行修改

codeceptjs的Page Object

codeceptjs gpo可以帮助你快速创建模板和进行配置

➜  codeceptjs-demo git:(master) ✗ codeceptjs gpo
Creating a new page object
--------------------------
? Name of a page object LandingPage
? Where should it be stored ./pages/LandingPage.js
Updating configuration file...
Page object for LandingPage was created in /Users/diyu/workspace/codeceptjs_demo/codeceptjs-demo/pages/LandingPage.js
Use landingPagePage as parameter in test scenarios to access it

模板文件会被创建在项目根目录下的pages目录中,内容如下

'use strict';
let I;

module.exports = {

  _init() {
    I = actor();
  }

  // insert your locators and methods here
}

把这个文件补充完整,就会变为下面这样

'use strict';
let I;

module.exports = {

  _init() {
    I = actor();
  },

  // setting locators
  fields: {
    email: '#user_basic_email',
    password: '#user_basic_password'
  },
  submitButton: {css: '#new_user_basic input[type=submit]'},

  // introducing methods
  sendForm(email, password) {
    I.fillField(this.fields.email, email);
    I.fillField(this.fields.password, password);
    I.click(this.submitButton);
  }
}

上面的page object文件写好之后,我们应该把它加入到测试文件中

Scenario('login', (I, loginPage) => {
  loginPage.sendForm('john@doe.com','123456');
  I.see('Hello, John');
});

还需要加入到在配置文件中

"include": {
  "I": "./steps_file.js",
  "landingPage": "./pages/landingPage.js"
}

generator在page object中的应用

我们可以在po文件中通过generator来使用grab*()方法

'use strict';
let I;

module.exports = {

  _init() {
    I = actor();
  },

  // setting locators
  container: "//div[@class = 'numbers']",
  mainItem: {
    number: ".//div[contains(@class, 'numbers__main-number')]",
    title: ".//div[contains(@class, 'numbers__main-title-block')]"
  },

  // introducing methods
  openMainArticle: function* () {
    I.waitForVisible(this.container)
    let _this = this
    let title;
    yield within(this.container, function*(){
      title = yield I.grabTextFrom(_this.mainItem.number);
      let subtitle = yield I.grabTextFrom(_this.mainItem.title);
      title = title + " " + subtitle.charAt(0).toLowerCase() + subtitle.slice(1);
      yield I.click(_this.mainItem.title)
    })
    return title;
  }
}
Scenario('login2', (I, mainPage, basePage) => {
  let title = yield* mainPage.openMainArticle()
  basePage.pageShouldBeOpened(title)
});

PageFragment

PageFragment是指在页面比较独立的部分,比如页面中一些独立组件(i.e. 弹出框)和小工具。我们可以把这可能每个页面都在用的公共部分抽取出来,单独的处理。

使用codeceptjs go --type fragment能够创建模板文件

➜  codeceptjs-init codeceptjs go --type fragment
Creating a new fragment object
--------------------------
? Name of a fragment object LandingPage
? Where should it be stored ./fragments/LandingPage.js
Updating configuration file...
Fragment object for LandingPage was created in /Users/diyu/workspace/codeceptjs_demo/codeceptjs-init/fragments/LandingPage.js
Use landingPageFragment as parameter in test scenarios to access it

配置文件如下

"include": {
  "I": "./steps_file.js",
  "landingPagePage": "./pages/landingPage.js",
  "landingPageStep": "./steps/LandingPage.js",
  "landingPageFragment": "./fragments/LandingPage.js"
},

对于page fragment推荐的使用方式是包含一个组件的root locator,然后在page fragment中的方法使用within()去缩小范围。

let I;
// fragments/modal.js
module.exports = {

  _init() {
    I = actor();
  },

  root: '#modal',

  // we are clicking "Accept: inside a popup window"
  accept() {
    within(this.root, function() {
      I.click('Accept');
    });
  }
}

StepObjects

StepObjects代表涉及多个页面的复杂操作流程,实现了业务价值。

使用codeceptjs go --type step能够创建模板文件。

➜  codeceptjs-init codeceptjs go --type step
Creating a new step object
--------------------------
? Name of a step object LandingPage
? Where should it be stored ./steps/LandingPage.js
Updating configuration file...
Step object for LandingPage was created in /Users/diyu/workspace/codeceptjs_demo/codeceptjs-init/steps/LandingPage.js
Use landingPageStep as parameter in test scenarios to access it

配置文件如下:

"include": {
  "I": "./steps_file.js",
  "landingPagePage": "./pages/landingPage.js",
  "landingPageStep": "./steps/LandingPage.js"
}
let I, userPage, permissionPage;
module.exports = {

  _init() {
    I = actor();
    userPage = require('../pages/user');
    userPage._init();
    permissionPage = require('../pages/permissions');
    permissionPage._init();
  },

  createUser(name) {
    // action composed from actions of page objects
    userPage.open();
    userPage.create(name);
    permissionPage.activate(name);
  }
};

Actor

在我们刚开始初始化的时候 codeceptjs init,有一个步骤会问你需要custom step files不。这个文件的目的是可以让你通过steps_file.js来扩展I对象的方法。

我们在steps_file.js中加入新的一个方法foo()

'use strict';
// in this file you can append custom step methods to 'I' object

module.exports = function() {
  return actor({

    // Define custom steps here, use 'this' to access default methods of I.
    // It is recommended to place a general 'login' function here.

    foo : function(){
        console.log('I am customized step foo for object I');
    }

  });
}

然后可以在测试文件中直接作为I的方法来进行调用了。

Feature('within demo');

Scenario('login github', {retries: 2}, (I, landingPage)=>{
    I.amOnPage('http://www.baidu.com');
    I.foo();
    landingPage.land();
    // I.see('test');
    I.say('Above is the link for baidu search engine');
});

要想在steps_file.js中使用I对象,需要使用self代替

'use strict';
// in this file you can append custom step methods to 'I' object

module.exports = function() {
  return actor({

    login: function(email, password) {
      this.fillField('Email', email);
      this.fillField('Password', password);
      this.click('Submit');
    }
  });
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,988评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,647评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,612评论 18 399
  • Android SDK API 19+ 开始支持 WebView的远程调试,配合 Chrome 的开发者工具可以...
    act262阅读 758评论 0 0
  • 某一天 遇见了你 我便不要天上的月亮了 只想要拥有尘世的幸福了
    伍月的晴空阅读 138评论 2 5