I am attempting to use Specflow to automate web tests using Selenium. So far, things are going mostly fine, but I am now running into a problem. One of my steps allow for a user to input a variable, the step looks like this:
Given I click the (VARIABLE) Menu
And the code behind it is fairly simple, just clicking on a link based on the text that is passed:
driver.FindElement(By.XPath("Xpath to get to the variable")).Click();
However, there is a later step that must use this information. That is fine, you can use "ScenarioContext.Current.Add(string, variable)" and I know about that and have been using it. It functions for the needs that I was first informed of.
My problem is that now the business wants to be able to add multiple items at the same time. This presents two problems. Attempting to just call the step a second time throws an exception: "An item with the same key has already been added." and if I put this into a Scenario Outline, which would allow me to call the variable a second time in a second run, I cannot use the first variable in the final step.
Logically, this means that passing in a variable multiple times is the problem (which makes sense, given it's passing in as a string) and so passing the variable in as an array seems the logical way to go. The idea is that when I pass the parameter from one step to another as an array instead of as a string I theoretically won't run into this error and then I will be able to iterate through the items in the array in that later step with a for loop. This seems like something that SpecFlow should be able to do, but I am having issues finding out just how to achieve this. Does anyone have an idea on how to do this? I attempted to merely use:
Scenario.Context.Current.Add(string, variable).ToArray();
However, that does not work, and all of the examples of "ToArray" I can find in the SpecFlow documentation doesn't seem to be actually changing the variables you pass from one step to another into an array, it seems to be used solely inside of individual steps and never passed between steps. Is passing parameters using ScenarioContext.Current.Add(string, variable) as an array possible in SpecFlow?
Thanks in advance.
the simplest solution to your problem is to add an array (or list) to the context in the first step and then to get it out and add to it and then replace it again in future steps:
List<string> list = new List<String>();
list.Add(variable)
ScenarioContext.Current.Add(name, list);
then later
List<String> currentList = (List<String>) ScenarioContext.Current[string];
currentList.Add(variable);
ScenarioContext.Current[name]=list;
However I feel duty bound to point out some issues with your current solution. You should investigate the PageObject pattern and hide your element selection XPath inside your page objects. Imagine the business decides to change the element that information is stored in. Now you have to change every test that does this:
driver.FindElement(By.XPath("Xpath to get to the variable")).Click();
for that variable. Using the page object pattern this is hidden inside the page object and you would only have a single place to change.
I personally would also consider sharing data using context injection as I find this allows strong typing of the data (so no cast is required like in the example above) and it allows you to know what data is stored, its not just a random bag of stuff).
Related
I have a private List<Experience> experiences; that tracks generic experiences and experience specific information. I am using Json Serialize and Deserialize to save and load my list. When you start the application the List populates itself with the current saved information automatically and when a new experience is added to the list it saves the new list to file.
A concern that is popping into my head that I would like to get ahead of is, there is nothing that would stop the user from at any point doing something like experiences = new List<Experience>(); and then adding new experiences to it. Saving this would result in a loss of all previous data as right now the file is overwritten with each save. In an ideal world, this wouldn't happen, but I would like to figure out how to better structure my code to guard against it. Essentially I want to disallow removing items from the List or setting the list to a new list after the list has already been populated from load.
I have toyed with the idea of just appending the newest addition to the file, but I also want to cover the case where you change properties of an existing item in the List, and given that the list will never be all that large of a file, I thought overwriting would be the simplest approach as the cost isn't a concern.
Any help in figuring out the best approach is greatly appreciated.
Edit* Looked into the repository pattern https://www.infoworld.com/article/3107186/application-development/how-to-implement-the-repository-design-pattern-in-c.html and this seems like a potential approach.
I'm making an assumption that your user in this case is a code-level consumer of your API and that they'll be using the results inside the same memory stack, which is making you concerned about reference mutation.
In this situation, I'd return a copy of the list rather than the list itself on read-operations, and on writes allow only add and remove as maccettura recommends in the comments. You could keep the references to the items in the list intact if you want the consumer to be able to mutate them, but I'd think carefully about whether that's appropriate for your use case and consider instead requiring the consumer to call an update function (which could be the same as your add function a-la HTTP PUT).
Sometimes when you want to highlight that your collection should not be modified, exposing it as an IEnumerable except List may be enough, but in case you are writing some serious API, something like repository pattern seems to, be a good solution.
I have a method which takes an OrderedSet X of objects of type A, and an OrderedSet Y of OrderedSets of objects of type B. (Nested)
This method then returns a new OrderedSet Z of Edges based on the two sets given.
Basically, I give the method two sets, and the method gives me back a connection much like the mathematical definition of a function.
So if I want to make a bijective connection, I would have to ensure that both sets have equal size, and I do not want null objects to be present anywhere.
The problem is, these sets are going to be arbitrarily large, and what I would like to do is this :
Ensure that nothing in these sets will ever be null
Ensure that every set has the required size
What I have done to obtain what I want :
I implemented the OrderedSet as a HashSet with extra properties, and I simply check for null elements using Contains which is O(1) (I am not entirely sure if this is a good solution)
OrderedSet refuses to add null objects, but this does not change the possibility of changing the elements within the set once they are added, and setting them to null this way
I tried taking the easy way out and nested the contents of the method in a try-catch, so if something goes wrong I simply catch the error and move on, rather than having to first validate all the data passed (Assume the sets might be very huge, there is no limitation on their size) The issue with this is that it might fail at the very end, wasting computation time
I also tried making a brute force check, so basically checking every set for nulls, including the sets within the second parameter, and also checking every single set for its correct size to be expected. This would work in theory, but I feel is impractical and surely there could be a more clever solution to this problem.
What I have considered :
I have looked into Contracts, but this article shows a significant (in my opinion) decrease in performance, although it does accomplish what I am looking for (Yet still in a hacky sort of way)
I have read about non-nullable reference types that are supposed to come out in C# 8.0, which might solve the problem of having to check for null elements, but I would still be left with the issue of having to check for all the sizes of the sets involved, each time I want to make a new connection.
My goal :
To have a readable, yet efficient solution to parameter validation.
Thank you for your time, please feel free to correct me if I have said something that is not quite correct.
I'm using ObjectListView with checkboxes, I would like to run a function on selected items to delete them. So i tried this Method but it not working:
private List<Matricule> matrs;
private void button1_Click(object sender, EventArgs e)
{
//List<Song> s = this.olvSongs.CheckedObjects.Count;
//MessageBox.Show(this.olvSongs.CheckedItems.Count + " " + this.olvSongs.CheckedObjects.Count);
string s = "";
foreach (var item in olvMatrs.SelectedItems)
{
matrs.Remove((Matricule)item);
}
this.olvSongs.SetObjects(matrs);
}
how can i do this task.
You talk about check boxes. The line
foreach (var item in olvMatrs.SelectedItems)
iterates through the ITEMS that are SELECTED, not CHECKED! Is that really what you want?
To get the CHECKED OBJECTS use
objectListView1.CheckedObjects
If you really want to get the SELECTED OBJECTS, don't use Selected*Items*. Use
objectListView1.SelectedObjects;
instead. Thats what the OLV is all about. You want to work with the objects, not with ListViewItems.
If you decided WHAT you want to remove, don't remove the objects from your List, but directly from your ObjectListView using
objectListView1.RemoveObjects(myObjects);
You should probably (re-)read this. Especially the section "Mental gear shift - This is important. You need to understand this.".
well, i see that you don't show the code where it adds anything to matrs, so we are certainly short of useful source code. Also, we don't know what a Matricule is, but i can take a pretty good guess with what you already shared.
i believe 1 of 3 things must be happening if matrs is not getting any items removed.
1: are you sure your function is tied to the click event of the button? you can set a break point in the function to make sure it is even executing. or you can add a line to show a messagebox MessageBox.Show("Yes", this.Text); inside that button1_Click() method.
2: if the function is being executed (so it is not option #1), then my 2nd consideration is that perhaps the (Matricule)item is not in the matrs List to be able to be deleted. that Remove function returns a boolean value indicating whether the remove actually deleted something or not.
3: are you sure it is not getting deleted and that what is really happening is that it really is being deleted but your new updated List is not being shown to you?
I believe you think it is #2, but might want to eliminated the possibility of the other 2 easier options (#1 and #3) first. if you do deduce it to be #2, so options #1 and #3 are not happening, then here's the thing with deleting objects by referencing those objects: it easily leads to problems like what you are having. it is so easy to have code that actually attempts to delete a new object with the same properties as another object that is in a List. the clean way that i solve this is try to remove items by their index # rather than a reference to the object itself. but you are not even grabbing the object to be deleted from the List itself. you are grabbing that object from olvMatrs, which is another object list. my best guess from the information you shared is that this is why it's not working, that if you look deeper, that you are trying to Remove an object that is not in the list, so nothing is being Removed. it's an easy mistake to make. i only know because i've done it too before i learned to be super careful about this.
I want to cascade the SyntaxHighlighting Engine of AvalonEdit. I have 2 HighlightingDefinitions. The first one is the main syntax. The second one is a complex multiline-preprocessor-markup-language. For this reason it is too complicated to embbed the second grammar in the first one. The easier way is to render the first syntax, and change the affected line-parts (based on the second syntax) afterwards.
So I instantiated a new HighlightingColorizer with the second language and added it to the LineTransformers. But the second language colorizes the complete document and not only the lineparts with the preprocessor-directives: the non-preprocessor-code is black.
As I debugged the ColorizeLine-method of the second line transformer, the lines of the non-highlighted code (= no preprocessor code) have not been colorized, as expected. But the color of the lines are black.
So does the HighlightingColorizer reset all previous highlighting of the whole document before it starts to colorize?
Or what else could be the problem? How can I properly cascade 2 HighlightingColorizers?
The problem is that the HighlightingColorizer does not directly store a reference to the DocumentHighlighter, but instead stores it via TextView.Services. This is done to allow attaching the same colorizer to multiple editors, so that each editor gets its own DocumentHighlighter.
When you attach a second colorizer, it overwrites the IHighlighter stored in the service container; and both colorizers end up using the new highlighter.
Also, note that the 'copy to clipboard' logic in HtmlClipboard directly accesses the IHighlighter service, it does not use any colorizers. (copying text to Word preserves the syntax highlighting only, no other transformations like fold markers)
There are essentially two approaches to solve this issue:
Do not store the additional highlighter as a service. You can do this by creating your own copy of the HighlightingColorizer class, and use a field in that class instead of accessing textView.Services. This is an easy change, but additional highlighters will not be used when copying text to the clipboard.
Create an IHighlighter implementation that combines the HighlightedLines from multiple DocumentHighlighters. This is the approach we are using for the C# semantic highlighting in SharpDevelop 5, which works as an additional highlighter that extends the existing .xshd-based C# highlighting. However, this approach is complex (merging HighlightedLines is non-trivial given the ordering and nesting constraints on the sections), and requires an API change to the IHighlighter interface in order to deal with the OnHighlightStateChanged notification (AvalonEdit 4.x uses a derived class nested in HighlightingColorizer to get access to this callback; AvalonEdit 5.0 will use an event).
I'm making a jquery clone for C#. Right now I've got it set up so that every method is an extension method on IEnumerable<HtmlNode> so it works well with existing projects that are already using HtmlAgilityPack. I thought I could get away without preserving state... however, then I noticed jQuery has two methods .andSelf and .end which "pop" the most recently matched elements off an internal stack. I can mimic this functionality if I change my class so that it always operates on SharpQuery objects instead of enumerables, but there's still a problem.
With JavaScript, you're given the Html document automatically, but when working in C# you have to explicitly load it, and you could use more than one document if you wanted. It appears that when you call $('xxx') you're essentially creating a new jQuery object and starting fresh with an empty stack. In C#, you wouldn't want to do that, because you don't want to reload/refetch the document from the web. So instead, you load it once either into a SharpQuery object, or into an list of HtmlNodes (you just need the DocumentNode to get started).
In the jQuery docs, they give this example
$('ul.first').find('.foo')
.css('background-color', 'red')
.end().find('.bar')
.css('background-color', 'green')
.end();
I don't have an initializer method because I can't overload the () operator, so you just start with sq.Find() instead, which operates on the root of the document, essentially doing the same thing. But then people are going to try and write sq.Find() on one line, and then sq.Find() somewhere down the road, and (rightfully) expect it to operate on the root of the document again... but if I'm maintaining state, then you've just modified the context after the first call.
So... how should I design my API? Do I add another Init method that all queries should begin with that resets the stack (but then how do I force them to start with that?), or add a Reset() that they have to call at the end of their line? Do I overload the [] instead and tell them to start with that? Do I say "forget it, no one uses those state-preserved functions anyway?"
Basically, how would you like that jQuery example to be written in C#?
sq["ul.first"].Find(".foo") ...
Downfalls: Abuses the [] property.
sq.Init("ul.first").Find(".foo") ...
Downfalls: Nothing really forces the programmer to start with Init, unless I add some weird "initialized" mechanism; user might try starting with .Find and not get the result he was expecting. Also, Init and Find are pretty much identical anyway, except the former resets the stack too.
sq.Find("ul.first").Find(".foo") ... .ClearStack()
Downfalls: programmer may forget to clear the stack.
Can't do it.
end() not implemented.
Use two different objects.
Perhaps use HtmlDocument as the base that all queries should begin with, and then every method thereafter returns a SharpQuery object that can be chained. That way the HtmlDocument always maintains the initial state, but the SharpQuery objects may have different states. This unfortunately means I have to implement a bunch of stuff twice (once for HtmlDocument, once for the SharpQuery object).
new SharpQuery(sq).Find("ul.first").Find(".foo") ...
The constructor copies a reference to the document, but resets the stack.
I think the major stumbling block you're running into here is that you're trying to get away with just having one SharpQuery object for each document. That's not how jQuery works; in general, jQuery objects are immutable. When you call a method that changes the set of elements (like find or end or add), it doesn't alter the existing object, but returns a new one:
var theBody = $('body');
// $('body')[0] is the <body>
theBody.find('div').text('This is a div');
// $('body')[0] is still the <body>
(see the documentation of end for more info)
SharpQuery should operate the same way. Once you create a SharpQuery object with a document, method calls should return new SharpQuery objects, referencing a different set of elements of the same document. For instance:
var sq = SharpQuery.Load(new Uri("http://api.jquery.com/category/selectors/"));
var header = sq.Find("h1"); // doesn't change sq
var allTheLinks = sq.Find(".title-link") // all .title-link in the whole document; also doesn't change sq
var someOfTheLinks = header.Find(".title-link"); // just the .title-link in the <h1>; again, doesn't change sq or header
The benefits of this approach are several. Because sq, header, allTheLinks, etc. are all the same class, you only have one implementation of each method. Yet each of these objects references the same document, so you don't have multiple copies of each node, and changes to the nodes are reflected in every SharpQuery object on that document (e.g. after allTheLinks.text("foo"), someOfTheLinks.text() == "foo".).
Implementing end and the other stack-based manipulations also becomes easy. As each method creates a new, filtered SharpQuery object from another, it retains a reference to that parent object (allTheLinks to header, header to sq). Then end is as simple as returning a new SharpQuery containing the same elements as the parent, like:
public SharpQuery end()
{
return new SharpQuery(this.parent.GetAllElements());
}
(or however your syntax shakes out.)
I think this approach will get you the most jQuery-like behavior, with a fairly easy implementation. I'll definitely be keeping an eye on this project; it's a great idea.
I would lean towards a variant on option 2. In jQuery $() is a function call. C# doesn't have global functions, a static function call is the closest. I would use a method that indicates you're creating a wrapper like..
SharpQuery.Create("ul.first").Find(".foo")
I wouldn't be concerned about shortening SharpQuery to sq since intellisense means users won't have to type the whole thing (and if they have resharper they only need to type SQ anyways).