Conflict error when creating AT content with robotframework library

I have a few Python keyword library keywords I'm using to create new content speedily from robots. They are:

def createObject(self, path, portal_type, id, **kwargs):
    site = getSite()
    container = site.restrictedTraverse(path.strip('/').split('/'))
    obj = _createObjectByType(portal_type, container, id, **kwargs)
    obj.unmarkCreationFlag()
    return obj.UID()

def createAR(self, path, **kwargs):
    portal = getSite()
    container = portal.restrictedTraverse(path.strip('/').split('/'))
    obj = create_analysisrequest(container, container.REQUEST, kwargs)
    return obj.getId()

I use createObject for great justice.

However my createAR causes me a conflict error. It's creating a few objects
(using just _createObjectByType() and unmarkCreationFlag()), but it fails to
create even the first object. Here's the much-abreviated call-path through PDB:

-> obj = create_analysisrequest(container, container.REQUEST, kwargs)
(Pdb) s
-> sample = create_sample(context, request, values)
(Pdb) s
-> sample = _createObjectByType('Sample', context, tmpID())
(Pdb) n
-> sample.unmarkCreationFlag()
(Pdb) import transaction;transaction.savepoint()
*** ConflictError: database conflict error (oid 0x..., class bika.lims.content.client.Client)

(bika.lims.content.client.Client) is the "context" in which the Sample is created

Before unmarkCreationFlag, I can commit the transaction just fine.

Immediately after unmarkCreationFlag(), I can trigger a Conflict error for this object by committing.

I use this code path directly from unit tests, with success. From robot-library however, something's in the way. I would really appreciate any tips on how to debug this kind of thing, or considerations I may be unaware of, any ideas of things I could have forgotton... I am at a loss now!

How are you importing and calling those functions from robot? How does the test look like?

This is probably a more generic functional testing than robot related issue. A possibility is that you are making database changes from two different connections (from the main test thread and through a worker thread by browser) and the main thread connection gets out of sync, causing conflicts. If that's the case, you need to manually sync the connection in the main thread: e.g. add portal._p_jar.sync() into createAR.

You are correct, this does seem to be the case. The python keyword library loses the authentication provided to the robot test by the autologin keyword.

The keyword creates objects successfully when I modify it like this:

def swapSecurityManager(self, userid):
    portal = api.portal.get()
    # remember the current SecurityManager
    saved = getSecurityManager()
    # switch to the selected user
    acl_users = getToolByName(portal, 'acl_users')
    user = acl_users.getUserById(userid)
    newSecurityManager(None, user)
    return saved

def createObject(self, path, portal_type, id, **kwargs):
    portal = api.portal.get()
    container = portal.restrictedTraverse(path.strip('/').split('/'))
    # login again
    saved = self.swapSecurityManager('test_labmanager')
    # create object
    obj = _createObjectByType(portal_type, container, id, **kwargs)
    obj.processForm(container.REQUEST, values=kwargs)
    # go back to original security manager
    setSecurityManager(saved)
    transaction.savepoint()
    return obj.UID()

But in this case, the object is no longer visible from the robot-test, when the keyword has returned.

Is there any commonly used construct that will allow the robot test and the keyword library to stay synchronised with each other?

[edit] The robot-test begins like this:

Enable autologin as  LabManager
set autologin username  test_labmanager
${cat_uid} =  getUID  catalog_name=bika_setup_catalog  portal_type=AnalysisCategory  title=Metals
${service_uid} =  createObject   bika_setup/bika_analysisservices  AnalysisService  s1  title=AL396152  Keyword=Al396152  Category=${cat_uid}
debug

And when the debug fires, the new object is not visible from within the Plone UI!

Yes.

To recap, the basic issue exists in any acceptance test, e.g. with zope.testbrowser. Your test suite code runs in the "main thread" while zope.testbrowser requests will be handled in worker threads. Therefore all changes must be committed before using the browser, and ZODB connection synced (if there were changes by browser) with obj._p_jar.sync() after continuing Python code.

Now the Robot Framework -solution. plone.app.robotframework has integration to execute all code through browser so that the syncing issue disappears. I named it "Robot Remote Library" and you already used it, because "Enable autologin" is defined there.

The solution, is to merge your custom keyword library to the remote library.

  1. Create a new keyword library, which inherits your library and plone.app.robotframework.remote.RemoteLibrary as e.g. in plone.app.robotframework/src/plone/app/robotframework/autologin.py at master · plone/plone.app.robotframework · GitHub

  2. Create a new Robot Remote test layer similarly to REMOTE_LIBRARY_BUNDLE_FIXTURE, but add also your library into it and use it instead of the original in your final test layer.
    plone.app.robotframework/src/plone/app/robotframework/testing.py at master · plone/plone.app.robotframework · GitHub

If you manage to do this, your keywords will be called through ZPublisher and you don't need manual commit or sync.

All-right, I finally got this working; I had originally intended to create my own BikaRobotRemote library, and although the library imported into the .robot file OK, robotframework would tell me that "there is no keyword called Create AR". I realised after a few hours of frustration that it seems necessary to augment the original RobotRemote library and import that.

Thank you!

Your work is beautiful; when I finally figured out what I was doing wrong, it was from reading your clear concise beautiful code that the solution was found.

Thank you again.

Great to hear that you figured it out. Augmenting (if I understood correctly) is the simplest way to get all the "remote" keywords. I'm sorry that the documentation is still missing for this.

Indeed, at least a snippet of this post would be nice in https://docs.plone.org/manage/troubleshooting/transactions.html#conflicterror