Finding Sub Spans within Element XPath using Protractor Framework - javascript

I have the following lines of code:
.then(function click(services) {
var tryItElement;
if (services.length < 1) {
return;
}
buttonElement = element(by.xpath('//div[#class="my-class" and contains(., \'' + services[0].title + '\')]//span[contains(., \'Button Text\')]'));
return browser.wait(protractor.ExpectedConditions.elementToBeClickable(buttonElement), getWaitTime())
.then(function() {
return buttonElement.getWebElement();
})
.then(function(buttonElement) {
var protocol = url.parse(services[0].actionUrl).protocol;
if (protocol === null) {
throw new Error('expected ' + protocol + ' not to be null');
}
})
.then(function() {
return buttonElement.click();
})
.then(function() {
return browser.wait(helper.ExpectedConditions.responseCompleted(proxy, services[0].actionUrl), getWaitTime());
})
.then(function() {
return browser.get(browser.baseUrl);
})
.then(function() {
return click(services.slice(1));
})
})
.catch(fail)
.then(done);
I expect the first to find an element on my page with the class that contains some text as specified from services[0].title. It does and the following line finds a span within that element that contains the text Button Text.
I have this in a loop and for some reason, the buttonElement stays the same even though performing the action manually in the Chrome dev tools gives me the expected results.
Any other ways of trying this or suggestions? I can clarify some more if needed.

Related

How to get js path of a DOM element?

I'd like to record all the interactions of the users of a web app so I can anlayze how they do use the application. In the first attempt I just want to store the js path of any element clicked. JSPath is to JSON what XPath is to xml.
On Chromium browsers you can get the js path of an element: just open de dev tools (F12), select the "Elements" tab (first one) and right click a node in the DOM and select Copy > Copy JS Path. You'll get the JS Path of the element in your clipboard, something like:
document.querySelector("#how-to-format > div.s-sidebarwidget--content.d-block > div:nth-child(5) > span:nth-child(4)")
If you pase the above code in the console you'll get a reference to the selected node. It's specially useful if the app contains web components so the JSPath contains the shadowRoot selector to traverse the dom to reach the element:
document.querySelector("#one").shadowRoot.querySelector("div > div")
On the first attempt, I think I can just listen for any click in dom and then record the jspath of the element that got the click
document.addEventListener('click', function (event){
//Get the jspath of the element
});
Is there any easy way to get the jspath of the event's target?
Thanks
After some research, I've not been able to find any lib at npm or github neither a response in stackoverflow that supplies what I did expect, so I decided to implement it. Below I'm pasting a simple typescript module that it does the trick. The hardest thing was to deal with slots.
//last 3 elements are window, document and html nodes. We don't need them
function shouldDismiss(tg): boolean {
return tg === window || tg === document || tg?.nodeName == 'HTML';
}
function isSlot(tg): boolean {
return tg?.nodeName === 'SLOT';
}
function isDocumentFragment(node): boolean {
return node?.nodeName === '#document-fragment';
}
function getNodeId(node) {
return node.id ? `#${node.id}` : ''
}
function getNodeClass(node) {
if (!node?.classList)
return '';
let classes = '';
for (let cssClass of node.classList)
classes += `.${cssClass}`
return classes;
}
function getNodeSelector(node) {
return `${node.localName || node.nodeName}${getNodeId(node)}${getNodeClass(node)}`
}
export function getEventJSPath(event: Event): string {
const path = event.composedPath();
let inSlot = false;
let jsPath = path.reduce((previousValue: string, currentValue: any) => {
if (shouldDismiss(currentValue))
return previousValue;
if (isSlot(currentValue)) {
inSlot = true;
return previousValue
}
if (inSlot) {
if (isDocumentFragment(currentValue))
inSlot = false;
return previousValue;
}
if (isDocumentFragment(currentValue))
return previousValue;
const selector = `.querySelector("${getNodeSelector(currentValue)}")${previousValue}`;
//if parent node is a document fragment we need to query its shadowRoot
return isDocumentFragment(currentValue?.parentNode) ? '.shadowRoot' + selector : selector
}, '');
jsPath = 'document' + jsPath;
//Check success on non production environments
if (process?.env != 'prod') {
try {
const el = eval(jsPath);
if (el != path[0]) {
debugger;
console.error('js path error');
}
}
catch (e) {
debugger;
console.error('js path error: ' + e.toString());
}
}
return jsPath;
}

How to improve flags in function?

I have the following code. I dislike this only aesthetically:
public createDocument() {
try {
if (this.isOpenDialog) throw 'Dialog already opened!';
this.loading = true;
this.isOpenDialog = true;
this.documentService.loadDocuments(this.application.reglamentid).then((response) => {
this.documentService.setTypeDocuments(response.typedocuments);
this.loading = false;
this.documentDialogFormService.open({ title: 'Документ', application: this.application }).subscribe(() => {
this.isOpenDialog = false;
});
});
} catch (e) {
console.log('ERROR: ' + e);
}
}
As you can see there are two flags: this.loading and this.isOpenDialog. First controls opening dialog, The second indicates loading.
Is it possible somehow to improve it?
The pattern you have is typically what people use since it is two distinct states for two different things. Reason why is the dialog could be loading and be shown at the same time. That means both could be true. Depending on what you do with it might make this the optimal solution.
If you want you could have one state that holds both. It might seem cleaner, but as you can see the check to see if it is opened is a bit more confusing since it has to check two states.
enum ModalStates {
Closed,
Loading,
Opened
}
public createDocument() {
try {
if (this.modalState === ModalStates.Loading || this.modalState === ModalStates.Opened ) throw 'Dialog already opened!';
this.modalState = ModalStates.Loading;
this.documentService.loadDocuments(this.application.reglamentid).then((response) => {
this.documentService.setTypeDocuments(response.typedocuments);
this.modalState = ModalStates.Opened
this.documentDialogFormService.open({
title: 'Документ',
application: this.application
}).subscribe(() => {
this.modalState = ModalStates.Closed
});
});
} catch (e) {
console.log('ERROR: ' + e);
}
}

VSCode exstension works while debugging but not once packaged into VSIX

I was trying to learn a little bit about building extensions for vscode and I've hit a road block. My extension is only supposed to paste from clipboard with a little extra text depending on the file type. (for quick debugging purposes) The problem is I have this working while debugging but once it is packaged I get a rejected promise not handled within 1 second: Error: TextEditor#edit not possible on closed editors
This makes sense to me but I can't figure out how to keep the activeTextEditor updated nor do I know why it would act different once packaged. I've gotta be missing something simple I'm sure.
function activate(context) {
const dbugText = vscode.commands.registerCommand('extension.dbug', () => {
vscode.env.clipboard.readText().then((text) => {
let editor = vscode.window.activeTextEditor;
if(!editor){
return;
}
var document = editor.document;
var selection = editor.selection;
var fileType = document.fileName.split('.')[1];
console.log(text);
if (fileType == 'php') {
text = "dbug(" + text + ");";
} else if (fileType == 'js') {
text = "console.log(" + text + ");";
} else {
console.log('Unknown file type');
return;
}
vscode.window.showInformationMessage(text);
editor.edit((editBuilder) => {
return editBuilder.insert(new vscode.Position(selection.start.line, selection.start.character), text);
})
.catch(err => console.error(err.message));
})
.catch(err => console.error(err.message));
});
context.subscriptions.push(dbugText);
}

Range.ParentTable giving itemNotFound exception

I have written my code in the process of inserting a table to the word document. My code worked successfully before, but when I run it now, it is giving an exception:
"ItemNotFound: ItemNotFound\n at Anonymous function (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:198669)\n
at yi (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:220646)\n
at st (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:220733)\n
at d (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:220553)\n
at c (https://appsforoffice.microsoft.com/lib/beta/hosted/word-win32-16.01.js:21:219139)"
The error location is showing as
"errorLocation":"Range.ParentTable"
I have no idea why the code is not running now. I have wrote this code in January. If anyone could, please help me to identify what is wrong in the below code.
my code :
this.insertTable = function () {
Word.run(function (context) {
var range = context.document.getSelection();
var tableDataJsonString;
if (range.parentTable != null) {
var parentTable = range.parentTable;
var parent_ContentController; //Content controller where the table has been inserted.
var parent_contentController_id;
var parent_contentController_tag;
var parent_name; //Section or Element name
var parent_id; //Section or Element's id
var table_style; //Table style
var count;
var header_rowCount;
var body_rowCount;
var columns;
var footer_rowCount;
parentTable.load('rowCount');
parentTable.load('headerRowCount');
return context.sync().then(function () {
if (parentTable.rowCount != null) {
}
}).then(function () {
existing_table.setTableProperties(parent_contentController_id, parent, parent_id, body_rowCount, columns, header_rowCount, footer_rowCount);
//tableDataJsonString = JSON.stringify(tableData);
context.sync();
if (count == null) {
_self.selectTableDlg(false, existing_table);
}
else if (count != 0) {
_self.selectTableDlg(true, existing_table);
}
}).catch(function (error) {
showAlert('Exception');
});
}
}).catch(function (error) {
console.log('Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
}
Code is not going through the 'return context.sync().then(function () {' and return an exception.
First of all, if the code was written back in January it was using the PREVIEW APIs, who are subject to change, we apologize about this and thanks for trying them!
On this case we have minor change associated on how NULL works, the error is how to check if the table exists. So effectively, you need to make a few changes to make your code work. btw There is a bit more detail on this answer who conceptually is the same issue as this one.
Here is some code on how you need to check if the selection is inside a table, and in general how we handle properties and methods that could return null.
Just adjust your code accordingly and it should work.
Word.run(function (context) {
//you can get parteTable (will rise an exception if null) or parentTableOrNullObject (never throws an exception and lets you check if its null or not using isNullObject property)
var myTable = context.document.getSelection().parentTableOrNullObject;
context.load(myTable);
return context.sync()
.then(function () {
if (myTable.isNullObject)
console.log("the selecion is NOT in a table");
else
console.log("the selection is within a table");
})
})

Protractor - Error: Index out of bound exception while using the same function for the second time

I have the following function which selects a category from a list of available categories. This function works fine in my first test. But the same function with a different valid category name in my second test fails with the following error.
Error: Index out of bound. Trying to access element at index: 0, but there are only 0 elements that match locator By.cssSelector(".grid-view-builder__category")
this.categoryElements = element.all(by.css('.grid-view-builder__category'));
this.selectCategory = function (categoryName) {
var filteredCategories = this.categoryElements.filter(function (category) {
return category.getText().then(function (text) {
log.info(text);
return text === categoryName;
})
})
filteredCategories.first().click().then(function () {
log.info("Select Category: " + categoryName);
}).then(null, function (err) {
log.error("Category: " + categoryName + " Not Found !!" + err);
});
}
Spec File
var columnSelect = require('pages/grid/columns/columnselector-page')()
it('Add Publisher ID Column to the Grid & Verify', function () {
var columnCountBefore = columnSelect.getColumnCount();
columnSelect.openColumnSelector();
columnSelect.selectCategory('Advanced');
columnSelect.selectColumn('Publisher ID');
columnSelect.apply();
var columnCountAfter = columnSelect.getColumnCount();
expect(columnCountAfter).toBeGreaterThan(columnCountBefore);
});
The problem might be in the way you are defining and using Page Objects. Here is a quick solution to try - if this would help, we'll discuss on why that is happening.
Make the categoryElements a function instead of being a property:
this.getCategoryElements = function () {
return element.all(by.css('.grid-view-builder__category'));
};
this.selectCategory = function (categoryName) {
var filteredCategories = this.getCategoryElements().filter(function (category) {
return category.getText().then(function (text) {
log.info(text);
return text === categoryName;
})
})
filteredCategories.first().click().then(function () {
log.info("Select Category: " + categoryName);
}).then(null, function (err) {
log.error("Category: " + categoryName + " Not Found !!" + err);
});
}
Or, this could be a "timing issue" - let's add an Explicit Wait via browser.wait() to wait for at least a single category to be present:
var EC = protractor.ExpectedConditions;
var category = element(by.css('.grid-view-builder__category'));
browser.wait(EC.presenceOf(category), 5000);
It looks like this has nothing to do with the code posted here, only that the css selector you're using is not finding any elements

Categories

Resources