Mink/Selenium can't find a button - selenium

I'm trying to run this scenario
#javascript
Scenario: Greeting in an alert box
Given I am on "/"
And I get content
When I press "say"
And I wait for the greeting to appear
Then I should see "Hello JavaScript!"
With my debug step
/**
* #Given I get content
*/
public function iGetContent(){
echo $this->getSession()->getPage()->getContent();
}
However, it reports it cannot find the button, when (I believe) it should be able to
#javascript
Scenario: Greeting in an alert box # features/home.feature:11
Given I am on "/" # FeatureContext::visit()
And I get content # FeatureContext::iGetContent()
│ <html lang="en"><head></head><body>
│ <h1>Home</h1>
│ <span id="js-greeting"></span>
│ <button type="button" id="say" value="say">say</button>
│
│
│ <script>
│ function showGreeting(){
│ document.getElementById("js-greeting").innerHTML = "Hello JavaScript!"
│ }
│
│ function delayedGreeting(){
│ window.setTimeout(showGreeting, 1000);
│ }
│ </script>
│
│ </body></html>
When I press "say" # FeatureContext::pressButton()
Button with id|name|title|alt|value "say" not found. (Behat\Mink\Exception\ElementNotFoundException)
And I wait for the greeting to appear # FeatureContext::iWaitForTheGreetingToAppear()
Then I should see "Hello JavaScript!" # FeatureContext::assertPageContainsText()
Selenium launches Firefox, and I can see that the page successfully loads, and I can physically see the button. Is there something I've missed? Here's my composer.json/behat .yml in case there's something here I've missed.
{
"name": "",
"description": "",
"keywords": [],
"repositories": [
{
"type": "vcs",
"url": "git#bitbucket.org:xxx.git"
}
],
"require": {
"php": "^5.6 || ^7.0",
"judgeservice/mvc": "dev-master",
"php-amqplib/php-amqplib": ">=2.6.1",
"behat/behat": "3.0.6",
"behat/mink": "1.6.*",
"behat/mink-extension": "*",
"behat/mink-selenium2-driver": "*",
"behat/mink-goutte-driver": "*",
"laravel/framework": "4.2.*",
"behat/mink-zombie-driver": "*"
},
"autoload": {
"psr-4": {
"Application\\": "module/Application/src/"
}
},
"require-dev": {
"phpunit/phpunit": "^5.7"
}
}
default:
extensions:
Behat\MinkExtension:
base_url: "http://api.example.com"
browser_name: 'firefox'
goutte: ~
selenium2: ~
public function pressButton($button)
{
$button = $this->fixStepArgument($button);
$this->getSession()->getPage()->pressButton($button);
}
protected function fixStepArgument($argument)
{
return str_replace('\\"', '"', $argument);
}
public function pressButton($locator)
{
$button = $this->findButton($locator);
if (null === $button) {
throw $this->elementNotFound('button', 'id|name|title|alt|value', $locator);
}
$button->press();
}
public function findButton($locator)
{
return $this->find('named', array(
'button', $this->getSelectorsHandler()->xpathLiteral($locator),
));
}
/**
* #Given I hit say
*/
public function iHitSay(){
$button = $this->getSession()
->getPage()
->find('css', '#say');
$button->press();
}

These solutions helped me:
https://github.com/minkphp/MinkSelenium2Driver/issues/293#issuecomment-519920991
https://github.com/Behat/MinkExtension/issues/345#issuecomment-510712489
The problem is starting with Chrome 75 version and above.

Related

Vue.js: Is it possible to do "Conditional compilation" with a Vue project?

In for example Swift/iOS development, it's possible to differentiate builds for different environments with "flags" such as:
#if STAGING
// one set of logic here
#endif
#if PRODUCTION
// another set of logic here
#endif
Is it possible to achieve the same with a Vue.js project, and how would we go about doing it? I am aware of makes different routes conditionally available for different roles (which is also quite neat), but I am optimally looking for the option to differentiate on a source code level.
Hope someone has some great insights! It could include:
How to exclude parts of a file (such as the #if STAGING above) from a build target
How to exclude entire files from a build target
etc.
you have the ability to use this syntax
if(process.env.NODE_ENV === 'production') {
console.log("this is the prod env!!!!!!!!!!");
config.output.path = path.resolve(__dirname, "dist");
}
make sure that when you run the script with the correct env's for each environment (local, dev, staging, prod etc ..) :D
just change the vue-loader output.
the source code
<template v-if="process.env.NODE_ENV === 'development'">
development only
</template>
default output
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
_vm.process.env.NODE_ENV === "development"
? [_vm._v(" development only ")]
: _vm._e(),
_c("router-view")
],
2
)
}
just use regex to replace _vm.process.env. by process.env is ok.
// webpack.config.js
module: {
rules: [{
// must set post
enforce: 'post',
test: /\.vue$/,
use: [{
loader: './myLoader'
}]
}]
}
// myLoader.js
module.exports = function (source, map) {
if (source.indexOf('_vm.process.env') > -1) {
source = source.replace(/_vm.process.env/g, 'process.env')
}
this.callback(
null,
source,
map
)
}
Final the vue-loader result change
var render = function() {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
{ attrs: { id: "app" } },
[
// change to true
true
? [_vm._v(" development only ")]
: undefined,
_c("router-view")
],
2
)
}

How can I use relative module path for client script by #NAmdConfig? SuiteScript 2.0

I would like to load non-AMD modules (jQuery and blockUI) using #NAmdConfig for client script, but my code occurs error in browser.
Uncaught Error: Module does not exist: ../../lib/jquery-blockUI.js
If I use absolute path instead of relative path, it works.
"baseUrl": "../../lib/"
Replace above with below, then works.
"baseUrl": "/SuiteScripts/ComponentA/SuiteScript2/lib/"
However I would like to use relative path because these scripts are going to be released as a bundle.
My current solution for this issue is using absolute path and replacing the path with bundle path when I release a bundle.
Does anyone know how to use relative path or better solution?
Script Files
File Structure
SuiteScripts/
└── ComponentA/
└── SuiteScript2/
├── FunctionA/
│ ├ config.json
│ ├ Suitelet.js
│ └ ClientScript.js
└── lib/
├ jquery.min.js
└ jquery-blockUI.js
config.json
{
"baseUrl": "../../lib/",
"paths": {
"jquery": "jquery.min.js",
"blockUI": "jquery-blockUI.js"
},
"shim": {
"blockUI": ["jquery"]
}
}
Suitelet.js
/**
* #NApiVersion 2.x
* #NScriptType Suitelet
* #NModuleScope SameAccount
* #NAmdConfig ./config.json
*/
define(['N/record', 'N/url', 'N/ui/serverWidget'],
function(record, nsUrl, serverWidget) {
function onRequest(context) {
// code abbreviated
var form = serverWidget.createForm({title: 'FunctionA', hideNavBar: false});
// Set client script
form.clientScriptModulePath = './ClientScript.js';
// code abbreviated
}
})
ClientScript.js
/**
* #NApiVersion 2.x
* #NScriptType ClientScript
* #NModuleScope SameAccount
* #NAmdConfig ./config.json
*/
define(['N/runtime', 'N/url', 'blockUI'],
function (runtime, url, blockUI) {
// code using blockUI
});
Like so. Its tricky at first but once you get it:
AMD config file ( /SuiteScripts/MyLibs/MyLibs.config.json ):
{
"packages": [
],
"paths": {
"sugar-2.0.4.min": "SuiteScripts/MyLibs/libs/sugar-2.0.4.min",
"buckets-1.98.2.min": "SuiteScripts/MyLibs/libs/buckets-1.98.2.min",
"jquery.jexcel-1.5.7": "SuiteScripts/MyLibs/libs/jquery.jexcel-1.5.7",
"jquery.jcalendar-1.5.7": "SuiteScripts/MyLibs/libs/jquery.jcalendar-1.5.7"
}
}
And the usage in a client script
*#NApiVersion 2.x
*#NScriptType ClientScript
*#NAmdConfig /SuiteScripts/MyLibs/MyLibs.config.json
*/
define([
'N/error',
'N/search',
'sugar-2.0.4.min',
'buckets-1.98.2.min',
'jquery.jexcel-1.5.7',
'jquery.jcalendar-1.5.7',
],
function (error, search, sugar, buckets, jexcel, jcalendar) {
function pageInit(context) {
// example 1 w/ sugar.js
var num = Sugar.Number.random(1, 100);
var hm = new buckets.Dictionary();
// example 2 w/ jquery grid
jQuery('#ui-grid').jexcel({
data: data,
colHeaders: ['Country', 'Description', 'Type', 'Stock', 'Next purchase'],
colWidths: [300, 80, 100, 60, 120],
columns: [
{type: 'autocomplete', url: 'https://bossanova.uk/jexcel/countries'},
{type: 'text'},
{
type: 'dropdown',
source: [{'id': '1', 'name': 'Fruits'}, {'id': '2', 'name': 'Legumes'}, {
'id': '3',
'name': 'General Food'
}]
},
{type: 'checkbox'},
{type: 'calendar'},
]
});
...
I found a workaround for this problem although it does not use relative path. jajo1987 told me this trick on Reddit, thanks jajo1987. Reddit
The workaround is having copy of config.json under /SuiteBundles/ folder in development environment.
With this trick, I don't have to replace path in config file when I release a bundle.
Script Files
Assuming bundle number is 00000.
File Structure
├── SuiteScripts/
│ └── ComponentA/
│ └── SuiteScript2/
│ ├── FunctionA/
│ │ ├ config.json
│ │ ├ Suitelet.js
│ │ └ ClientScript.js
│ └── lib/
│ ├ jquery.min.js
│ └ jquery-blockUI.js
└── SuiteBundles/
└── Bundle 00000/
└── SuiteScript2/
└── FunctionA/
└ config.json
/SuiteScripts/ComponentA/SuiteScript2/FunctionA/config.json
{
"baseUrl": "/SuiteBundles/Bundle 00000/SuiteScript2/lib/",
"paths": {
"jquery": "jquery.min.js",
"blockUI": "jquery-blockUI.js"
},
"shim": {
"blockUI": ["jquery"]
}
}
/SuiteBundles/Bundle 00000/SuiteScript2/FunctionA/config.json
{
"baseUrl": "/SuiteScripts/ComponentA/SuiteScript2/lib/",
"paths": {
"jquery": "jquery.min.js",
"blockUI": "jquery-blockUI.js"
},
"shim": {
"blockUI": ["jquery"]
}
}
ClientScript.js
/**
* #NApiVersion 2.x
* #NScriptType ClientScript
* #NModuleScope SameAccount
* #NAmdConfig /SuiteBundles/ComponentA/SuiteScript2/FunctionA/config.json
*/
define(['N/runtime', 'N/url', 'blockUI'],
function (runtime, url, blockUI) {
// code using blockUI
});

overriding actions for botium webdriverio connector

edit: change topic to overriding basic functons on botium-webdriverio-connector
How to trigger button click ?
from this link:
https://github.com/codeforequity-at/botium-core/wiki/Botium-Scripting
it says that you just need to put
#me
BUTTON btnTitle
but what actually happened is that it sends literal BUTTON btnTitle as text to the bot
for context, i implemented a custom WEBDRIVERIO_GETBOTMESSAGE and send back this
{
"sender": "bot",
"buttons": [
{
"text": "reply1"
},
{
"text": "reply2"
}
],
"cards": [],
"media": []
}
i decided to override the WEBDRIVERIO_SENDTOBOT function.
running on botium-cli 0.0.44, probably there are better solutions on newer versions.
edit: add more details, source code & implementation
in the end, i override these functions WEBDRIVERIO_OPENBOT WEBDRIVERIO_SENDTOBOT WEBDRIVERIO_GETBOTMESSAGE and also add a custom asserter to assert gallery messages
botium.json
{
"botium": {
"Capabilities": {
"PROJECTNAME": "Production Test",
"CONTAINERMODE": "webdriverio",
"WEBDRIVERIO_OPTIONS": {
"desiredCapabilities": {
"browserName": "chrome"
}
},
"WEBDRIVERIO_URL": "the bot url",
"WEBDRIVERIO_OPENBOT": "./actions/open_test",
"WEBDRIVERIO_IGNOREWELCOMEMESSAGES": 1,
"WEBDRIVERIO_SENDTOBOT": "./actions/send",
"WEBDRIVERIO_GETBOTMESSAGE": "./actions/parse_response",
"WEBDRIVERIO_START_SELENIUM": true,
"WEBDRIVERIO_START_SELENIUM_OPTS": {
"drivers": {
"chrome": {
"version": "2.36"
}
}
},
"ASSERTERS": [
{
"ref": "GALLERY",
"src": "./asserters/gallery",
"global": true
}
]
}
}
}
./actions/open_test
module.exports = (container, browser) => {
return browser
.waitForVisible('#toggle-chat', 20000)
.click('#toggle-chat') // click chat button
.pause(2000)
.waitForVisible('#get-started', 20000)
.click('#get-started') // click get started, initiate the chat
.pause(2000)
}
./actions/send
module.exports = (container, browser, msg) => {
if (msg.messageText && msg.messageText !== '' && msg.messageText.indexOf('BUTTON') !== 0) { // send text message
return browser
.waitForEnabled('#text-input', 10000)
.setValue('#text-input', msg.messageText)
.keys('Enter')
} else if (msg.messageText.indexOf('BUTTON') === 0) { // if message started with "BUTTON xxx", safe to assume the tester want to click button xxx
let buttonTitle = msg.messageText.split(' ')[1]
if (!buttonTitle) throw new Error('BUTTON invalid (1)')
return browser.waitForEnabled(`[chatmessagebuttontitle="${ buttonTitle }"]`, 10000)
.click(`[chatmessagebuttontitle="${ buttonTitle }"]`)
} else { // unhandled, send arbitary message
return browser
.waitForEnabled('#text-input', 10000)
.setValue('#text-input', 'unhandled')
.keys('Enter')
}
}
./actions/parse_response
module.exports = (container, browser, elementId) => {
const botMsg = { sender: 'bot', buttons: [], cards: [], media: [] }
/**
*
* ... parsing some html here to populate buttons, media and cards
* ... can't put all the code for security reasons, sorry
* ... here are example
*
* btw, elementId is NOT an html id attribute, so cannot query with "#" + elementId
* cannot find documentation for webdriver elementId, but this page helps
* http://v4.webdriver.io/api/protocol/elementIdElements.html
*
*/
browser.elementIdElements(elementId, '.bot-bubble')
.then(elements => elements.value)
.then(elements =>
Promise.all(
elements.map(element =>
browser.elementIdText(element.ELEMENT).then(text => text.value)
)
).then(messages => {
// i only need the first message, also this function only called for each bot message bubble, so safe to assume there is only 1 message
if (messages.length > 0) botMsg.messageText = messages[0]
// send the bot response back to botium
container.BotSays(botMsg)
})
)
}
./asserter/gallery
/**
* #typedef Card
* #property {String} image
* #property {String} title
* #property {String} subtitle
* #property {Array<String>} buttons
*
* #typedef BotMesage
* #property {Array<Card>} cards
*/
/**
* #typedef GalleryAssertStepParam
* #property {*} convo
* #property {Array<String>} args
* #property {BotMesage} botMsg
*/
module.exports = class GalleryAsserter {
/**
* this function is called whenever parse_response.js finishes
* #param {GalleryAssertStepParam} param
*/
assertConvoStep(param) {
let args = param.args
let botMsg = param.botMsg
// args needs to be an array, simple check
if (!args.concat) return Promise.reject(new Error('args for GALLERY is not an array'))
if (args.length > botMsg.cards.length) return Promise.reject(new Error('number of gallery cards doesnt match. expecting ' + args.length +'. Got ' + botMsg.cards.length))
// basic logic to check if the card that is passed from parse_response.js is equals to card that is written in test scripts
for (var i = 0; i < args.length; i++) {
// ParseGalleryString is a function that convert arbitary string to a Card object
// example of the arbitary string: "( title=some title, subtitle= some subtitle, image=image url, buttons=btn1, btn2, btn3 )"
// will be converted to JSON { title: "some title", subtitle: "some subtitle", ... }
let card = ParseGalleryString(args[i])
let testcard = botMsg.cards[i]
if (card.image !== testcard.image) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting image to be ${ card.image }, got ${ testcard.image }`))
if (card.title !== testcard.title) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting title to be ${ card.title }, got ${ testcard.title }`))
if (card.subtitle !== testcard.subtitle) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting subtitle to be ${ card.subtitle }, got ${ testcard.subtitle }`))
if (card.buttons.length !== testcard.buttons.length) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting ${ card.buttons.length }(${card.buttons.join(', ')}) buttons, got ${ testcard.buttons.length }(${testcard.buttons.join(', ')})`))
if (card.buttons.join('_') !== testcard.buttons.join('_')) return Promise.reject(new Error(`card[${i}] doesn't pass. expecting buttons to be ${ card.buttons.join(', ') }, got ${ testcard.buttons.join(', ') }`))
}
return Promise.resolve()
}
}
then to use the custom asserter
testgallery.convo.txt
Test Gallery
#me
show me some cat breeds
#bot
here are some cat breeds
#bot
GALLERY (image=http://cat.pic/1.png, title=Alaskan Malmute, subtitle=This is actually not a cat, buttons=Pet, Give Food, Info) | (image=http://cat.pic/2.png, title=Cobra Kai, subtitle=This is actually a movie, buttons=Watch, Like, Dislike)
this setup works for botium-cli version 0.0.44
tried to update to botium 0.0.45 but got some error on selenium driver
Triggering button clicks is part of Botium Core 1.4.8. For button clicks the result of WEBDRIVERIO_GETBOTMESSAGE is not considered, Botium just looks for button titles (source code with default selenium selector is here.
The described behaviour, that the literal text is sent to the bot, is most likely due to an older version of Botium Core. Please check the version you are using. If you are using version 1.4.8, please enable logging and extend your question with the log output.

Testing elm lib with local Native directory

How do we organize our test directory when developing some libraries that uses Native js code?
I tried to work this out, but I'm blocked here, with this error at runtime when running test/test.sh:
Elm.Native.Mylib = {};
^
TypeError: Cannot read property 'Native' of undefined
git repository
My directories are structured this way:
Mylib:
- src :
- Mylib.elm
- Native :
- MyLib.js
- tests :
- Test.elm
- Test.sh
- elm-package.json
the tests/elm-package.json contains :
{
"version": "1.0.0",
"summary": "helpful summary of your project, less than 80 characters",
"repository": "https://github.com/user/project.git",
"license": "BSD3",
"source-directories": [
"."
,"../src"
],
"exposed-modules": [],
"native-modules": true,
"dependencies": {
"elm-community/elm-test": "1.1.0 <= v < 2.0.0",
"elm-lang/core": "4.0.1 <= v < 5.0.0"
},
"elm-version": "0.17.0 <= v < 0.18.0"
}
the tests/Test.elm is :
module Main exposing (..)
import Basics exposing (..)
import ElmTest exposing (..)
import Mylib exposing (..)
tests : Test
tests =
suite "elm-Mylib Library Tests"
[ ]
main =
runSuite tests
The tests/test.sh is
#!/bin/sh
elm-package install -y
elm-make --yes --output test.js Test.elm
node test.js
The src/Mylib.elm is
module Mylib exposing (..)
import Native.Mylib exposing (..)
import Task exposing (Task)
import Time exposing (Time)
print : a -> Task x ()
print value =
Native.Mylib.log (toString value)
getCurrentTime : Task x Time
getCurrentTime =
Native.Mylib.getCurrentTime
The src/Native/Mylib.js is
Elm.Native.Mylib = {};
Elm.Native.Mylib.make = function(localRuntime) {
localRuntime.Native = localRuntime.Native || {};
localRuntime.Native.Mylib = localRuntime.Native.Mylib || {};
if (localRuntime.Native.Mylib.values)
{
return localRuntime.Native.Mylib.values;
}
var Task = Elm.Native.Task.make(localRuntime);
var Utils = Elm.Native.Utils.make(localRuntime);
function log(string)
{
return Task.asyncFunction(function(callback) {
console.log(string);
return callback(Task.succeed(Utils.Tuple0));
});
}
var getCurrentTime = Task.asyncFunction(function(callback) {
return callback(Task.succeed(Date.now()));
});
return localRuntime.Native.Mylib.values = {
log: log,
getCurrentTime: getCurrentTime
};
};
Try this:
var _user$project$Native_MyLib = function() {
return {
exported: function(arg) { return "One" },
exported2: F2(function(arg) { return "Two" }),
exported3: F3(function(arg) { return "Three" }),
}
}();
It works for grater than Elm 0.17.
Buy you should also use full qualified import:
import Natve.MyLib
exported : String -> String
Native.MyLib.exported
exported2 : String -> String -> String
Native.MyLib.exported2
exported3 : String -> String -> String -> String
Native.MyLib.exported3
User and project values are from your/local elm-package.json:
"repository": "https://github.com/user/project.git",

How to remap a Durandal viewmodel

This is my main.js file in a Durandal project.
What I'm trying to do is set things up so that the name 'upload-item' resolves to either 'upload-item' or 'upload-item-prehtml5' depending on whether File is defined.
requirejs.config({
paths: {
'text': '../lib/require/text',
'durandal': '../lib/durandal/js',
'plugins': '../lib/durandal/js/plugins',
'transitions': '../lib/durandal/js/transitions',
'knockout': '../lib/knockout/knockout-2.3.0',
'bootstrap': '../lib/bootstrap/js/bootstrap',
'jquery': '../lib/jquery/jquery-1.9.1.min',
'jquery-ui': '../lib/jquery-ui/jquery-ui-1.10.4.custom/js/jquery-ui-1.10.4.custom.min',
'moment': '../lib/moment/moment',
'knockout-jqueryui': '../lib/knockout/knockout-jqueryui.min',
'file-size-formatting': '../lib/wone/file-size-formatting'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
define(['durandal/system', 'durandal/app', 'durandal/viewLocator'], function (system, app, viewLocator) {
//>>excludeStart("build", true);
system.debug(true);
//>>excludeEnd("build");
var filetype = typeof(File);
if (filetype == 'undefined') {
//apply pre-html5 fixups
require.config({
map: {
'*': {
'upload-item': 'upload-item-prehtml5'
}
}
});
}
app.title = 'Jumbo File Transfer';
//specify which plugins to install and their configuration
app.configurePlugins({
router: true,
dialog: true,
widget: {
kinds: ['expander']
}
});
app.start().then(function () {
//Replace 'viewmodels' in the moduleId with 'views' to locate the view.
//Look for partial views in a 'views' folder in the root.
viewLocator.useConvention();
//Show the app by setting the root view model for our application.
app.setRoot('shell');
});
});
Testing on IE8 shows that the call to require.config occurs and the mapping is added, but it doesn't seem to have the effect I expected: upload-item.js and upload-item.html are loaded when I expected upload-item-prehtml5.js and upload-item-prehtml5.html to be loaded.
If this is the wrong way to go about this, then what is the right way to perform this kind of conditional resolution?
It's not quite what I originally wanted, but I found you can do this:
var prehtml5 = (typeof (File) == 'undefined');
requirejs.config({
paths: {
...
'upload-item': prehtml5 ? 'upload-item-prehtml5' : 'upload-item'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
Path remapping seems to extend into the file name. Normally you wouldn't list siblings of main.js but you can and if you do then you can remap them, including the file name.