I want to use a workflow similar to the Page Object Pattern that exists in frameworks like Selenium. I want to use my login.spec.js in my editSettings.spec.js, because it requires a user to be logged in.
How do I achieve this in Cypress? Can I export a function from one test file to use in another?
Yes, Cypress supports the ability to create and reuse actions in your UI, such as logging in as a user would.
However, Cypress also allows you to control the state of the browser more powerfully than a user would.
For example: I create a test that a "user can log in with valid username and password"- Cypress navigates to the login page, types in the user field, types in the password field and clicks the "Log in" button. The Page Object Pattern would have you reuse this action on every test that requires a user to be logged in (most of the tests)
Cypress supports this; however, this is slower than it has to be. It takes a considerable amount of time to navigate to a login page, type in the information, handle the response, and navigate to the page under test.
Instead, Cypress's API allows the following:
use cy.request() to directly hit your server with the login credentials. This requires no state of your app, no typing in fields, no clicking buttons, or page directs
Any cookies your site uses are automatically set, or you can set localStorage using the response
Make this a custom command, call it before every test, and boom- you've generated your user's state almost instantly and most importantly flake-free
I actually came up with these two examples, one using JavaScript and another one with Typescript.
https://github.com/antonyfuentes/cypress-typescript-page-objects
https://github.com/antonyfuentes/cypress-javascript-page-objects
Hopefully, this helps someone else.
Create a SearchProduct.js file in fixtures folder (You can create it anywhere).Then create a class in it and define your all the methods in it something like this:
class ProductPage {
getSearchClick() {
return cy.get('.noo-search');
}
getSearchTextBox(){
return cy.get('.form-control');
}
getProductsName() {
return cy.get('.noo-product-inner h3');
}
getSelectSize() {
return cy.get('#pa_size');
}
getSelectColor() {
return cy.get('#pa_color');
}
getAddtoCartButton() {
return cy.get('.single_add_to_cart_button');
}
}
export default ProductPage
After creating the class, let's import it in the command.js file. After that, let's create a new object of it to access all the methods mentioned above in commands.js.
import ProductPage from '../support/PageObjects/ProductPage';
Cypress.Commands.add("selectProduct", (productName, size , color) => {
// Creating Object for ProductPage
const productPage=new ProductPage();
// Doing the search part for Shirts.
productPage.getSearchClick().click()
productPage.getSearchTextBox().type('Shirt');
productPage.getSearchTextBox().type('{enter}')
productPage.getProductsName().each(($el , index , $list) => {
//cy.log($el.text());
if($el.text().includes(productName)) {
cy.get($el).click();
}
})
// Selecting the size and color and then adding to cart button.
productPage.getSelectColor().select(color);
productPage.getSelectSize().select(size);
productPage.getAddtoCartButton().click();
})
So, here actually the custom command's class is importing and using the Page class. Additionally, the test script will use the same command.js to perform the needed action.
So, the test script will still be the same and will look as below:
// type definitions for Cypress object "cy"
// <reference types="cypress" />
describe('Cypress Page Objects and Custom Commands', function() {
//Mostly used for Setup Part
before(function(){
cy.fixture('example').then(function(data)
{
this.data=data ;
})
})
it('Cypress Test Case', function() {
//Registration on the site
cy.visit('https://shop.demoqa.com/my-account/');
cy.get('#reg_username').type(this.data.Username);
cy.get('#reg_email').type(this.data.Email);
cy.get('#reg_password').type(this.data.NewPassword);
cy.get('.woocommerce-Button').click();
//Checking whether the Registration is successful and whether UserName is populated under login section
cy.get('#username').should('have.value',this.data.Username);
})
// For Loop for Accessing productName array from Features File and Using the custom command
this.data.productName.forEach(function(element){
// Invoke the Custom command selectProduct
cy.selectProduct(element[0],element[1],element[2]);
})
})
You can also directly import the class in Test File by skipping Command.js file.
For that go to the following link:
Courtesy: https://softans.com/cypress-page-object-model/
Related
I am using feature files in my Cypress framework.
Below is an example of a scenario:
Scenario: #1 Cancel should return to Customer Management landing page
Given User is on the Edit Customer Page
When User updates the email address to "abc#gmail.com"
Then the updated email address will appear on the summary page
The problem I'm facing is that when I re-run this test, the original email address will be "abc#gmail.com" (the value it was updated to during the first test run). So the test won't actually update the email address.
What is the best approach to deal with this issue?
I was thinking of using something like a Before() function to delete the customer if it exists, & re-create it. Then I'd be able to update it & the values will be the same each time.
However, I don't know how to add a Before() in a feature file. I have a Background that navigates to a certain page, but is that the place where I should be putting it?
The cypress-cucumber-preprocessor supports both Mocha's before/beforeEach/after/afterEach hooks and Cucumber's Before and After hooks. So in your step Definition file, you can:
//This example is with the Cucumber hooks
const {
Before,
After,
Given,
Then,
} = require('cypress-cucumber-preprocessor/steps')
// this will get called before each scenario
Before(() => {
//Code to delete customer if it exists
})
TheBrainFamily recommend using Given
Please see Step definitions
This is a good place to put before/beforeEach/after/afterEach hooks related to that particular feature. This is incredibly hard to get right with pure cucumber.
So
Add codition to Given
Use beforeEach()
import { Given } from "cypress-cucumber-preprocessor/steps";
const url = 'https://google.com'
Given('User is on the Edit Customer Page And Email is empty', () => {
beforeEach(() => {
// reset email
})
...
})
I'm trying to create dynamic pages based on a database that grows by the minute. Therefor it isn't an option to use createPage and build several times a day.
I'm using onCreatePage here to create pages which works fine for my first route, but when I try to make an English route somehow it doesn't work.
gatby-node.js:
exports.onCreatePage = async ({ page, actions: { createPage } }) => {
if (page.path.match(/^\/listing/)) {
page.matchPath = '/listing/:id'
createPage(page)
}
if (page.path.match(/^\/en\/listing/)) {
page.matchPath = '/en/listing/:id'
createPage(page)
}
}
What I'm trying to achieve here is getting 2 dynamic routes like:
localhost:8000/listing/123 (this one works)
localhost:8000/en/listing/123 (this one doesn't work)
My pages folder looks like this:
pages
---listing.tsx
---en/
------listing.tsx
Can anyone see what I'm doing wrong here?
--
P.S. I want to use SSR (available since Gatsby v4) by using the getServerData() in the templates for these pages. Will that work together with pages created dynamically with onCreatePage or is there a better approach?
According to what we've discussed in the comment section: the fact that the /en/ path is never created, hence is not entering the following condition:
if (page.path.match(/^\/en\/listing/)) {
page.matchPath = '/en/listing/:id'
createPage(page)
}
Points me to think that the issue is on your createPages API rather than onCreatePage, which means that your english page is not even created.
Keep in mind that onCreatePage API is a callback called when a page is created, so it's triggered after createPages.
If you add a console.log(page.path) you shouldn't see the English page in the IDE/text editor console so try debugging how are you creating the /en/ route because it seems that onCreatePage doesn't have any problem.
Is there a simple way to modify a dashlet to automatically re-load itself periodically?
I am particularly thinking of the "My Tasks" dashlet - we are using pooled review workflows, so tasks may come and go all the time as they are created and then are claimed.
It may be frustrating for users to keep clicking on tasks that turn out to have already been claimed - or having to remember to keep re-loading their Dashboard page. I'd prefer the dashlet to refresh on a timed interval so it's always reasonably up to date.
In order to do this you will need to add a new capability to the client-side class Alfresco.dashlet.MyTasks (docs, source) found in the file components/dashlets/my-tasks.get.js. First you will need to add a new method to the prototype extension specified as the second parameter in the YAHOO.lang.augmentObject() call, e.g.
...
}, // end of last OOTB function - add a comment here
// begin changes
reloadData: function MyTasks_onReady()
{
this.widgets.alfrescoDataTable.loadDataTable(
this.options.filters[this.widgets.filterMenuButton.value]
);
}
// end changes
});
})();
It's not the ideal development environment, you can modify the JS file directly in the Share webapp, although you will also need to update the corresponding -min.js file.
Once you've done this, check that it works by running the following line in your browser's JavaScript console
Alfresco.util.ComponentManager.findFirst("Alfresco.dashlet.MyTasks").reloadData();
If that works, then you can wire up your new method to a title bar action (see my DevCon presentation for more background info), in the dashlet web script. The method depends on whether you are using v4.2 or a previous version, but if it is the latter then you need to add some code to the dashlet's Freemarker file my-tasks.get.html.ftl (under WEB-INF/classes/alfresco/site-webscripts/org/alfresco/components/dashlets).
In that file you should see some JavaScript code inside a <script> tag, this sets up an instance of the client-side class and some utility classes, the contents of which you can replace with the following, to add your custom title bar action.
(function()
{
var dashlet = new Alfresco.dashlet.MyTasks("${jsid}").setOptions(
{
hiddenTaskTypes: [<#list hiddenTaskTypes as type>"${type}"<#if type_has_next>, </#if></#list>],
maxItems: ${maxItems!"50"},
filters:
{<#list filters as filter>
"${filter.type?js_string}": "${filter.parameters?js_string}"<#if filter_has_next>,</#if>
</#list>}
}).setMessages(${messages});
new Alfresco.widget.DashletResizer("${id}", "${instance.object.id}");
var refreshDashletEvent = new YAHOO.util.CustomEvent("onDashletRefresh");
refreshDashletEvent.subscribe(dashlet.reloadData, dashlet, true);
new Alfresco.widget.DashletTitleBarActions("${args.htmlid}").setOptions(
{
actions:
[
{
cssClass: "refresh",
eventOnClick: refreshDashletEvent,
tooltip: "${msg("dashlet.refresh.tooltip")?js_string}"
},
{
cssClass: "help",
bubbleOnClick:
{
message: "${msg("dashlet.help")?js_string}"
},
tooltip: "${msg("dashlet.help.tooltip")?js_string}"
}
]
});
})();
You will need to add some styles for the class name specified, in the dashlet's CSS file my-tasks.css, such as the following
.my-tasks .titleBarActions .refresh
{
display: none;
background-image: url('refresh-icon.png');
}
The icon file (here is one you could re-use) must be in the same directory as the CSS file.
Lastly you'll need to define the label dashlet.refresh.tooltop used for the title bar action's tooltip. You can do this in the dashlet web script's .properties file.
For a similar example, check out the source of my Train Times dashlet, which features a refresh title bar action.
In some ways it's actually easier to define your own dashlets than it is to extend the Alfresco-supplied ones, but if you have the option of using 4.2.x, the new method allows you to extend the existing components without duplicating any code, which obviously makes upgrades much easier.
I've been searching for examples and reference and have come up with nothing. I found a note in offscreenTab source code mentioning it cannot be instantiated from a background page (it doesn't have a tab for the offscreenTab to relate to). Elsewhere I found mention that popup also has no tie to a tab.
How do you successfully create an offscreenTab in a Chrome extension?
According to the documentation, offscreenTabs.create won't function in a background page. Although not explicitly mentioned, the API cannot be used in a Content script either. Through a simple test, it seems that the popup has the same limitation as a background page.
The only leftover option is a tab which runs in the context of a Chrome extension. The easiest way to do that is by using the following code in the background/popup:
chrome.tabs.create({url: chrome.extension.getURL('ost.htm'), active:false});
// active:false, so that the window do not jump to the front
ost.htm is a helper page, which creates the tab:
chrome.experimental.offscreenTabs.create({url: '...'}, function(offscreenTab) {
// Do something with offscreenTab.id !
});
To change the URL, use chrome.experimental.offscreenTabs.update.
offscreenTab.id is a tabId, which ought to be used with the chrome.tabs API. However, at least in Chrome 20.0.1130.1, this is not the case. All methods of the tabs API do not recognise the returned tabID.
A work-around is to inject a content script using a manifest file, eg:
{"content_scripts": {"js":["contentscript.js"], "matches":["<all_urls>"]}}
// contentscript.js:
chrome.extension.sendMessage({ .. any request .. }, function(response) {
// Do something with response.
});
Appendum to the background page:
chrome.extension.onMessage.addListener(function(message, sender, sendResponse) {
// Instead of checking for index == -1, you can also see if the ID matches
// the ID of a previously created offscreenTab
if (sender.tab && sender.tab.index === -1) {
// index is negative if the tab is invisible
// ... do something (logic) ...
sendResponse( /* .. some response .. */ );
}
});
With content scripts, you've got full access to a page's DOM. But not to the global object. You'll have to inject scripts (see this answer) if you want to run code in the context of the page.
Another API which might be useful is the chrome.webRequest API. It can be used to modify headers/abort/redirect requests. Note: It cannot be used to read or modify the response.
Currently, the offscreenTabs API is experimental. To play with it, you have to enable the experimental APIs via chrome://flags, and add "permissions":["experimental"] to your manifest file. Once it's not experimental any more, use "permissions":["offscreenTabs"].
I have a safecracker form that submits an entry. The form consists of title, url_title, and description. I want to create an extension hook that filters out certain words if they exist in the title of the entry.
I already have a function that take care of the cleaning function clean(){....}. I understand that we need to use an extension hook so we can clean the title upon saving the entry.
What extension hook do i need to use for that. can you give me a complete example of an extension hook. I'm very good with PHP but still new to hooks and how they should be implemented. I already read the EE documentation but still find some confusion of how a hook is used
First head over to http://pkg.io/ and get your base extension file.
You'll probably want to use the 'safecracker_submit_entry_start' hook to throw an error if unclean word is entered. The most important part of the extension is registering the method and hook you want to use, otherwise none of the code will run.
Your code should look something like this:
public function activate_extension()
{
// Setup custom settings in this array.
$this->settings = array();
$data = array(
'class' => __CLASS__,
'method' => 'clean', // point to the method that should run
'hook' => 'safecracker_submit_entry_end', // point to the hook you want to use to trigger the above method.
'settings' => serialize($this->settings),
'version' => $this->version,
'enabled' => 'y'
);
$this->EE->db->insert('extensions', $data);
}
Once the method has been called you can start your cleaning. Make sure you pass the safecracker object to your clean method when defining it. For example:
public function clean($sc){
print_r($sc);
}